git.iko.soy/distributed-process
on b49566a 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 (very preliminary)
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 (proof of concept)
17+* azure-service-api: Haskell bindings for the Azure service API
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://www.haskell.org/haskellwiki/Cloud_Haskell
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+44
@@ -0,0 +1,44 @@
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 Microsoft Azure Service Management API
7+Description: At the moment, this module only provides minimal
8+ functionality, just enough to support the
9+ "distributed-process-azure" package (Azure backend for
10+ Cloud Haskell). However, the code is set up in such a way
11+ that adding additional functionality should be relatively
12+ straightforward; developers who wish to do will probably
13+ want to consult the Windows Azure Service Management REST
14+ API Reference
15+ (<http://msdn.microsoft.com/en-us/library/windowsazure/ee460799.aspx>).
16+Homepage: github.com/haskell-distributed/azure-service-api
17+License: BSD3
18+License-File: LICENSE
19+Author: Edsko de Vries
20+Maintainer: edsko@well-typed.com
21+Copyright: Well-Typed LLP
22+Category: Network
23+Build-Type: Simple
24+Cabal-Version: >=1.8
25+
26+Library
27+ Exposed-Modules: Network.Azure.ServiceManagement
28+ Build-Depends: base >= 4.5 && < 5,
29+ http-conduit >= 1.8.1 && < 1.9,
30+ tls >= 1.0 && < 1.1,
31+ tls-extra >= 0.5 && < 0.6,
32+ bytestring >= 0.9 && < 0.11,
33+ certificate >= 1.3 && < 1.4,
34+ case-insensitive >= 0.4 && < 0.5,
35+ hxt >= 9.2 && < 9.4,
36+ hxt-xpath >= 9.1 && < 9.2,
37+ pretty >= 1.1 && < 1.2,
38+ crypto-pubkey-types >= 0.1 && < 0.2,
39+ binary >= 0.5 && < 0.7,
40+ resourcet >= 0.4 && < 0.5,
41+ transformers >= 0.3 && < 0.4
42+ HS-Source-Dirs: src
43+ ghc-options: -Wall
44+ Extensions: RankNTypes
azure-service-api/src/Network/Azure/ServiceManagement.hsnew file+319
@@ -0,0 +1,319 @@
1+{-# LANGUAGE Arrows #-}
2+module Network.Azure.ServiceManagement
3+ ( -- * Data types
4+ CloudService
5+ , cloudServiceName
6+ , cloudServiceVMs
7+ , VirtualMachine
8+ , vmName
9+ , vmIpAddress
10+ , vmInputEndpoints
11+ , Endpoint
12+ , endpointName
13+ , endpointPort
14+ , endpointVip
15+ -- * Pure functions
16+ , vmSshEndpoint
17+ -- * Setup
18+ , AzureSetup(..)
19+ , azureSetup
20+ -- * High-level API
21+ , cloudServices
22+ ) where
23+
24+import Prelude hiding (id, (.))
25+import Control.Category (id, (.))
26+import Control.Arrow (arr)
27+import Control.Monad (forM)
28+import Control.Applicative ((<$>), (<*>))
29+import Data.Maybe (listToMaybe)
30+import Data.ByteString.Lazy (ByteString)
31+import Data.ByteString.Char8 as BSC (pack)
32+import Data.ByteString.Lazy.Char8 as BSLC (unpack)
33+import Data.Binary (Binary(get,put))
34+import Data.Binary.Put (runPut)
35+import Data.Binary.Get (Get, runGet)
36+import Network.TLS (PrivateKey(PrivRSA))
37+import Network.TLS.Extra (fileReadCertificate, fileReadPrivateKey)
38+import Data.Certificate.X509 (X509, encodeCertificate, decodeCertificate)
39+import qualified Crypto.Types.PubKey.RSA as RSA (PrivateKey(..))
40+import Control.Monad.Trans.Resource (ResourceT)
41+import Control.Monad.IO.Class (liftIO)
42+import Control.Arrow.ArrowList (listA, arr2A)
43+import Text.PrettyPrint
44+ ( Doc
45+ , text
46+ , (<+>)
47+ , ($$)
48+ , vcat
49+ , hang
50+ , doubleQuotes
51+ )
52+import Network.HTTP.Conduit
53+ ( parseUrl
54+ , clientCertificates
55+ , requestHeaders
56+ , withManager
57+ , Response(Response)
58+ , httpLbs
59+ , Manager
60+ )
61+import Data.CaseInsensitive as CI (mk)
62+import Text.XML.HXT.Core
63+ ( runX
64+ , readString
65+ , withValidate
66+ , no
67+ , XmlTree
68+ , IOSArrow
69+ , ArrowXml
70+ , getText
71+ )
72+import Text.XML.HXT.XPath (getXPathTrees)
73+
74+--------------------------------------------------------------------------------
75+-- Data types --
76+--------------------------------------------------------------------------------
77+
78+data HostedService = HostedService {
79+ hostedServiceName :: String
80+ }
81+
82+-- | A cloud service is a bunch of virtual machines that are part of the same
83+-- network (i.e., can talk to each other directly using standard TCP
84+-- connections).
85+data CloudService = CloudService {
86+ -- | Name of the service.
87+ cloudServiceName :: String
88+ -- | Virtual machines that are part of this cloud service.
89+ , cloudServiceVMs :: [VirtualMachine]
90+ }
91+
92+-- | Virtual machine
93+data VirtualMachine = VirtualMachine {
94+ -- | Name of the virtual machine.
95+ vmName :: String
96+ -- | The /internal/ IP address of the virtual machine (that is, the
97+ -- IP address on the Cloud Service). For the globally accessible IP
98+ -- address see 'vmInputEndpoints'.
99+ , vmIpAddress :: String
100+ -- | Globally accessible endpoints to the virtual machine
101+ , vmInputEndpoints :: [Endpoint]
102+ }
103+
104+-- | Globally accessible endpoint for a virtual machine
105+data Endpoint = Endpoint {
106+ -- | Name of the endpoint (typical example: @SSH@)
107+ endpointName :: String
108+ -- | Port number (typical examples are 22 or high numbered ports such as 53749)
109+ , endpointPort :: String
110+ -- | Virtual IP address (that is, globally accessible IP address).
111+ --
112+ -- This corresponds to the IP address associated with the Cloud Service
113+ -- (i.e., that would be returned by a DNS lookup for @name.cloudapp.net@,
114+ -- where @name@ is the name of the Cloud Service).
115+ , endpointVip :: String
116+ }
117+
118+--------------------------------------------------------------------------------
119+-- Pretty-printing --
120+--------------------------------------------------------------------------------
121+
122+instance Show HostedService where
123+ show = show . ppHostedService
124+
125+instance Show CloudService where
126+ show = show . ppCloudService
127+
128+instance Show VirtualMachine where
129+ show = show . ppVirtualMachine
130+
131+instance Show Endpoint where
132+ show = show . ppEndpoint
133+
134+ppHostedService :: HostedService -> Doc
135+ppHostedService = text . hostedServiceName
136+
137+ppCloudService :: CloudService -> Doc
138+ppCloudService cs =
139+ (text "Cloud Service" <+> (doubleQuotes . text . cloudServiceName $ cs))
140+ `hang2`
141+ ( text "VIRTUAL MACHINES"
142+ `hang2`
143+ (vcat . map ppVirtualMachine . cloudServiceVMs $ cs)
144+ )
145+
146+ppVirtualMachine :: VirtualMachine -> Doc
147+ppVirtualMachine vm =
148+ (text "Virtual Machine" <+> (doubleQuotes . text . vmName $ vm))
149+ `hang2`
150+ ( text "IP" <+> text (vmIpAddress vm)
151+ $$ ( text "INPUT ENDPOINTS"
152+ `hang2`
153+ (vcat . map ppEndpoint . vmInputEndpoints $ vm)
154+ )
155+ )
156+
157+ppEndpoint :: Endpoint -> Doc
158+ppEndpoint ep =
159+ (text "Input endpoint" <+> (doubleQuotes . text . endpointName $ ep))
160+ `hang2`
161+ ( text "Port" <+> text (endpointPort ep)
162+ $$ text "VIP" <+> text (endpointVip ep)
163+ )
164+
165+hang2 :: Doc -> Doc -> Doc
166+hang2 d1 = hang d1 2
167+
168+--------------------------------------------------------------------------------
169+-- Pure operations --
170+--------------------------------------------------------------------------------
171+
172+-- | Find the endpoint with name @SSH@.
173+vmSshEndpoint :: VirtualMachine -> Maybe Endpoint
174+vmSshEndpoint vm = listToMaybe
175+ [ ep
176+ | ep <- vmInputEndpoints vm
177+ , endpointName ep == "SSH"
178+ ]
179+
180+--------------------------------------------------------------------------------
181+-- Setup --
182+--------------------------------------------------------------------------------
183+
184+-- | Azure setup
185+--
186+-- The documentation of "distributed-process-azure" explains in detail how
187+-- to obtain the SSL client certificate and the private key for your Azure
188+-- account.
189+--
190+-- See also 'azureSetup'.
191+data AzureSetup = AzureSetup
192+ { -- | Azure subscription ID
193+ subscriptionId :: String
194+ -- | SSL client certificate
195+ , certificate :: X509
196+ -- | RSA private key
197+ , privateKey :: PrivateKey
198+ -- | Base URL (generally <https://management.core.windows.net>)
199+ , baseUrl :: String
200+ }
201+
202+-- TODO: it's dubious to be transferring private keys, but we transfer them
203+-- over a secure connection and it can be argued that it's safer than actually
204+-- storing the private key on each remote server
205+
206+encodePrivateKey :: PrivateKey -> ByteString
207+encodePrivateKey (PrivRSA pkey) = runPut $ do
208+ put (RSA.private_size pkey)
209+ put (RSA.private_n pkey)
210+ put (RSA.private_d pkey)
211+ put (RSA.private_p pkey)
212+ put (RSA.private_q pkey)
213+ put (RSA.private_dP pkey)
214+ put (RSA.private_dQ pkey)
215+ put (RSA.private_qinv pkey)
216+
217+decodePrivateKey :: ByteString -> PrivateKey
218+decodePrivateKey = PrivRSA . runGet getPrivateKey
219+ where
220+ getPrivateKey :: Get RSA.PrivateKey
221+ getPrivateKey =
222+ RSA.PrivateKey <$> get <*> get <*> get <*> get <*> get <*> get <*> get <*> get
223+
224+instance Binary AzureSetup where
225+ put (AzureSetup sid cert pkey url) = do
226+ put sid
227+ put (encodeCertificate cert)
228+ put (encodePrivateKey pkey)
229+ put url
230+ get = do
231+ sid <- get
232+ Right cert <- decodeCertificate <$> get
233+ pkey <- decodePrivateKey <$> get
234+ url <- get
235+ return $ AzureSetup sid cert pkey url
236+
237+-- | Initialize Azure
238+azureSetup :: String -- ^ Subscription ID
239+ -> String -- ^ Path to certificate
240+ -> String -- ^ Path to private key
241+ -> IO AzureSetup
242+azureSetup sid certPath pkeyPath = do
243+ cert <- fileReadCertificate certPath
244+ pkey <- fileReadPrivateKey pkeyPath
245+ return AzureSetup {
246+ subscriptionId = sid
247+ , certificate = cert
248+ , privateKey = pkey
249+ , baseUrl = "https://management.core.windows.net"
250+ }
251+
252+--------------------------------------------------------------------------------
253+-- High-level API --
254+--------------------------------------------------------------------------------
255+
256+-- | Find available cloud services
257+cloudServices :: AzureSetup -> IO [CloudService]
258+cloudServices setup = azureExecute setup $ \exec -> do
259+ services <- exec hostedServicesRequest
260+ forM services $ \service -> do
261+ roles <- exec AzureRequest {
262+ relativeUrl = "/services/hostedservices/" ++ hostedServiceName service
263+ ++ "?embed-detail=true"
264+ , apiVersion = "2012-03-01"
265+ , parser = proc xml -> do
266+ role <- getXPathTrees "//Role[@type='PersistentVMRole']" -< xml
267+ name <- getText . getXPathTrees "/Role/RoleName/text()" -< role
268+ roleInst <- arr2A getXPathTrees -< ("//RoleInstance[RoleName='" ++ name ++ "']", xml)
269+ ip <- getText . getXPathTrees "/RoleInstance/IpAddress/text()" -< roleInst
270+ endpoints <- listA (parseEndpoint . getXPathTrees "//InputEndpoint") -< role
271+ id -< VirtualMachine name ip endpoints
272+ }
273+ return $ CloudService (hostedServiceName service) roles
274+
275+hostedServicesRequest :: AzureRequest HostedService
276+hostedServicesRequest = AzureRequest
277+ { relativeUrl = "/services/hostedservices"
278+ , apiVersion = "2012-03-01"
279+ , parser = arr HostedService
280+ . getText
281+ . getXPathTrees "//ServiceName/text()"
282+ }
283+
284+parseEndpoint :: ArrowXml t => t XmlTree Endpoint
285+parseEndpoint = proc endpoint -> do
286+ name <- getText . getXPathTrees "//Name/text()" -< endpoint
287+ port <- getText . getXPathTrees "//Port/text()" -< endpoint
288+ vip <- getText . getXPathTrees "//Vip/text()" -< endpoint
289+ id -< Endpoint name port vip
290+
291+--------------------------------------------------------------------------------
292+-- Low-level API --
293+--------------------------------------------------------------------------------
294+
295+data AzureRequest c = AzureRequest {
296+ relativeUrl :: String
297+ , apiVersion :: String
298+ , parser :: IOSArrow XmlTree c
299+ }
300+
301+azureExecute :: AzureSetup -> ((forall b. AzureRequest b -> ResourceT IO [b]) -> ResourceT IO a) -> IO a
302+azureExecute setup f = withManager (\manager -> f (go manager))
303+ where
304+ go :: Manager -> forall b. AzureRequest b -> ResourceT IO [b]
305+ go manager request = do
306+ req <- parseUrl $ baseUrl setup
307+ ++ "/" ++ subscriptionId setup
308+ ++ "/" ++ relativeUrl request
309+ let req' = req {
310+ clientCertificates = [ (certificate setup, Just $ privateKey setup) ]
311+ , requestHeaders = [ (CI.mk $ BSC.pack "x-ms-version", BSC.pack $ apiVersion request)
312+ , (CI.mk $ BSC.pack "content-type", BSC.pack "application/xml")
313+ ]
314+ }
315+ Response _ _ _ lbs <- httpLbs req' manager
316+ liftIO . runX $ proc _ -> do
317+ xml <- readString [withValidate no] (BSLC.unpack lbs) -< ()
318+ -- arrIO putStrLn . writeDocumentToString [withIndent yes] -< xml
319+ 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+102
@@ -0,0 +1,102 @@
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, duncan@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+Description: This is a proof of concept Azure backend for Cloud Haskell. It
15+ provides just enough functionality to run Cloud Haskell
16+ applications on Azure virtual machines. You need to create your
17+ virtual machines in the Azure management portal; you can then
18+ use this backend to copy or verify your executable to the
19+ virtual machine, start or terminate Cloud Haskell nodes on those
20+ virtual machines, and communicate with those virtual machines
21+ from your local machine.
22+Category: Control
23+
24+Source-Repository head
25+ Type: git
26+ Location: https://github.com/haskell-distributed/distributed-process
27+ SubDir: distributed-process-azure
28+
29+Flag build-demos
30+ description: Build the demos
31+ default: False
32+
33+Library
34+ Build-Depends: base >= 4.4 && < 5,
35+ azure-service-api >= 0.1 && < 0.2,
36+ filepath >= 1.3 && < 1.4,
37+ executable-path >= 0.0.3 && < 0.1,
38+ libssh2 >= 0.2 && < 0.3,
39+ pureMD5 >= 2.1 && < 2.2,
40+ bytestring >= 0.9 && < 0.11,
41+ distributed-process >= 0.3.2 && < 0.5,
42+ binary >= 0.5 && < 0.7,
43+ network-transport >= 0.3 && < 0.4,
44+ network-transport-tcp >= 0.3 && < 0.4,
45+ transformers >= 0.3 && < 0.4,
46+ certificate >= 1.3 && < 1.4,
47+ unix >= 2.5 && < 2.7,
48+ mtl >= 2.1 && < 2.2,
49+ rank1dynamic >= 0.1 && < 0.2,
50+ distributed-static >= 0.2 && < 0.3
51+ Exposed-modules: Control.Distributed.Process.Backend.Azure
52+ Extensions: ViewPatterns,
53+ RankNTypes,
54+ ExistentialQuantification,
55+ ScopedTypeVariables,
56+ DeriveDataTypeable,
57+ GeneralizedNewtypeDeriving
58+ ghc-options: -Wall
59+ HS-Source-Dirs: src
60+
61+Executable cloud-haskell-azure-echo
62+ if flag(build-demos)
63+ Build-Depends: base >= 4.4 && < 5,
64+ distributed-process-azure >= 0.1 && < 0.2,
65+ distributed-process >= 0.3.2 && < 0.5,
66+ transformers >= 0.3 && < 0.4
67+ else
68+ buildable: False
69+ Main-Is: demos/Echo.hs
70+ ghc-options: -Wall
71+
72+Executable cloud-haskell-azure-ping
73+ if flag(build-demos)
74+ Build-Depends: base >= 4.4 && < 5,
75+ distributed-process-azure >= 0.1 && < 0.2,
76+ distributed-process >= 0.3.2 && < 0.5,
77+ transformers >= 0.3 && < 0.4,
78+ bytestring >= 0.9 && < 0.11,
79+ binary >= 0.5 && < 0.7,
80+ mtl,
81+ libssh2
82+ else
83+ buildable: False
84+ Main-Is: demos/Ping.hs
85+ ghc-options: -Wall
86+
87+Executable cloud-haskell-azure-fib
88+ if flag(build-demos)
89+ Build-Depends: base >= 4.4 && < 5,
90+ distributed-process-azure >= 0.1 && < 0.2,
91+ distributed-process >= 0.3.2 && < 0.5,
92+ transformers >= 0.3 && < 0.4,
93+ bytestring >= 0.9 && < 0.11,
94+ binary >= 0.5 && < 0.7,
95+ binary-generic >= 0.2 && < 0.3,
96+ rank1dynamic >= 0.1 && < 0.2,
97+ distributed-static >= 0.2 && < 0.3,
98+ random >= 1.0 && < 1.1
99+ else
100+ buildable: False
101+ Main-Is: demos/Fib.hs
102+ 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+-- uncharted 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 (void, 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" -> void $ 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+154
@@ -0,0 +1,154 @@
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+ other-modules: MasterSlave
29+ PrimeFactors
30+ hs-source-dirs: src/MasterSlave src/Common
31+ build-depends: base ==4.5.*,
32+ distributed-process >= 0.3.2
33+ extensions: BangPatterns, CPP, TemplateHaskell
34+ ghc-options: -Wall
35+ if flag(use-simplelocalnet)
36+ build-depends: distributed-process-simplelocalnet
37+ cpp-options: -DUSE_SIMPLELOCALNET
38+ else
39+ buildable: False
40+
41+executable distributed-process-azure-masterslave
42+ main-is: Azure.hs
43+ other-modules: MasterSlave
44+ PrimeFactors
45+ hs-source-dirs: src/MasterSlave src/Common
46+ build-depends: base ==4.5.*,
47+ distributed-process >= 0.3.2
48+ extensions: BangPatterns, CPP, TemplateHaskell
49+ ghc-options: -Wall
50+ if flag(use-azure)
51+ build-depends: distributed-process-azure
52+ cpp-options: -DUSE_AZURE
53+ else
54+ buildable: False
55+
56+executable distributed-process-simplelocalnet-workpushing
57+ main-is: SimpleLocalnet.hs
58+ other-modules: WorkPushing
59+ PrimeFactors
60+ hs-source-dirs: src/WorkPushing src/Common
61+ build-depends: base ==4.5.*,
62+ distributed-process >= 0.3.2
63+ extensions: BangPatterns, CPP, TemplateHaskell
64+ ghc-options: -Wall
65+ if flag(use-simplelocalnet)
66+ build-depends: distributed-process-simplelocalnet
67+ cpp-options: -DUSE_SIMPLELOCALNET
68+ else
69+ buildable: False
70+
71+executable distributed-process-azure-workpushing
72+ main-is: Azure.hs
73+ other-modules: WorkPushing
74+ PrimeFactors
75+ hs-source-dirs: src/WorkPushing src/Common
76+ build-depends: base ==4.5.*,
77+ distributed-process >= 0.3.2
78+ extensions: BangPatterns, CPP, TemplateHaskell
79+ ghc-options: -Wall
80+ if flag(use-azure)
81+ build-depends: distributed-process-azure
82+ cpp-options: -DUSE_AZURE
83+ else
84+ buildable: False
85+
86+executable distributed-process-simplelocalnet-typedworkpushing
87+ main-is: SimpleLocalnet.hs
88+ other-modules: TypedWorkPushing
89+ PrimeFactors
90+ hs-source-dirs: src/TypedWorkPushing src/Common
91+ build-depends: base ==4.5.*,
92+ distributed-process >= 0.3.2
93+ extensions: BangPatterns, CPP, TemplateHaskell
94+ ghc-options: -Wall
95+ if flag(use-simplelocalnet)
96+ build-depends: distributed-process-simplelocalnet
97+ cpp-options: -DUSE_SIMPLELOCALNET
98+ else
99+ buildable: False
100+
101+executable distributed-process-azure-typedworkpushing
102+ main-is: Azure.hs
103+ other-modules: TypedWorkPushing
104+ PrimeFactors
105+ hs-source-dirs: src/TypedWorkPushing src/Common
106+ build-depends: base ==4.5.*,
107+ distributed-process >= 0.3.2
108+ extensions: BangPatterns, CPP, TemplateHaskell
109+ ghc-options: -Wall
110+ if flag(use-azure)
111+ build-depends: distributed-process-azure
112+ cpp-options: -DUSE_AZURE
113+ else
114+ buildable: False
115+
116+executable distributed-process-simplelocalnet-workstealing
117+ main-is: SimpleLocalnet.hs
118+ other-modules: WorkStealing
119+ PrimeFactors
120+ hs-source-dirs: src/WorkStealing src/Common
121+ build-depends: base ==4.5.*,
122+ distributed-process >= 0.3.2
123+ extensions: BangPatterns, CPP, TemplateHaskell
124+ ghc-options: -Wall
125+ if flag(use-simplelocalnet)
126+ build-depends: distributed-process-simplelocalnet
127+ cpp-options: -DUSE_SIMPLELOCALNET
128+ else
129+ buildable: False
130+
131+executable distributed-process-simplelocalnet-mapreduce
132+ main-is: SimpleLocalnet.hs
133+ other-modules: CountWords
134+ KMeans
135+ MapReduce
136+ MonoDistrMapReduce
137+ PolyDistrMapReduce
138+ hs-source-dirs: src/MapReduce
139+ build-depends: base ==4.5.*,
140+ distributed-process >= 0.3.2,
141+ distributed-static >= 0.2.0 && < 0.3,
142+ containers >= 0.4 && < 0.6,
143+ bytestring >= 0.9 && < 0.11,
144+ binary >= 0.5 && < 0.7,
145+ array >= 0.4 && < 0.5,
146+ random >= 1.0 && < 1.1
147+ extensions: BangPatterns, CPP, TemplateHaskell, ScopedTypeVariables
148+ ghc-options: -Wall -rtsopts -with-rtsopts=-K64M
149+ if flag(use-simplelocalnet)
150+ build-depends: distributed-process-simplelocalnet
151+ cpp-options: -DUSE_SIMPLELOCALNET
152+ else
153+ buildable: False
154+
distributed-process-demos/src/Common/PrimeFactors.hsnew file+25
@@ -0,0 +1,25 @@
1+-- | Prime factorization
2+--
3+-- Written by Dan Weston
4+-- <http://westondan.blogspot.ie/2007/07/simple-prime-factorization-code.html>
5+module PrimeFactors where
6+
7+primes :: [Integer]
8+primes = primes' (2:[3,5..])
9+ where
10+ primes' (x:xs) = x : primes' (filter (notDivisorOf x) xs)
11+ notDivisorOf d n = n `mod` d /= 0
12+
13+factors :: [Integer] -> Integer -> [Integer]
14+factors qs@(p:ps) n
15+ | n <= 1 = []
16+ | m == 0 = p : factors qs d
17+ | otherwise = factors ps n
18+ where
19+ (d,m) = n `divMod` p
20+
21+primeFactors :: Integer -> [Integer]
22+primeFactors = factors primes
23+
24+numPrimeFactors :: Integer -> Integer
25+numPrimeFactors = fromIntegral . length . primeFactors
distributed-process-demos/src/MapReduce/CountWords.hsnew file+34
@@ -0,0 +1,34 @@
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
12+import MonoDistrMapReduce hiding (__remoteTable)
13+
14+type Document = String
15+type Word = String
16+type Frequency = Int
17+
18+countWords :: MapReduce FilePath Document Word Frequency Frequency
19+countWords = MapReduce {
20+ mrMap = const (map (, 1) . words)
21+ , mrReduce = const sum
22+ }
23+
24+localCountWords :: Map FilePath Document -> Map Word Frequency
25+localCountWords = localMapReduce countWords
26+
27+countWords_ :: () -> MapReduce FilePath Document Word Frequency Frequency
28+countWords_ () = countWords
29+
30+remotable ['countWords_]
31+
32+distrCountWords :: [NodeId] -> Map FilePath Document -> Process (Map Word Frequency)
33+distrCountWords = distrMapReduce ($(mkClosure 'countWords_) ())
34+
distributed-process-demos/src/MapReduce/KMeans.hsnew file+127
@@ -0,0 +1,127 @@
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
19+import PolyDistrMapReduce hiding (__remoteTable)
20+
21+type Point = (Double, Double)
22+type Cluster = (Double, Double)
23+
24+average :: Fractional a => [a] -> a
25+average xs = sum xs / fromIntegral (length xs)
26+
27+distanceSq :: Point -> Point -> Double
28+distanceSq (x1, y1) (x2, y2) = a * a + b * b
29+ where
30+ a = x2 - x1
31+ b = y2 - y1
32+
33+nearest :: Point -> [Cluster] -> Cluster
34+nearest p = minimumBy (compare `on` distanceSq p)
35+
36+center :: [Point] -> Point
37+center ps = let (xs, ys) = unzip ps in (average xs, average ys)
38+
39+kmeans :: Array Int Point -> MapReduce (Int, Int) [Cluster] Cluster Point ([Point], Point)
40+kmeans points = MapReduce {
41+ mrMap = \(lo, hi) cs -> [ let p = points ! i in (nearest p cs, p)
42+ | i <- [lo .. hi]
43+ ]
44+ , mrReduce = \_ ps -> (ps, center ps)
45+ }
46+
47+localKMeans :: Array Int Point
48+ -> [Cluster]
49+ -> Int
50+ -> Map Cluster ([Point], Point)
51+localKMeans points cs iterations = go (iterations - 1)
52+ where
53+ mr :: [Cluster] -> Map Cluster ([Point], Point)
54+ mr = localMapReduce (kmeans points) . trivialSegmentation
55+
56+ go :: Int -> Map Cluster ([Point], Point)
57+ go 0 = mr cs
58+ go n = mr . map snd . Map.elems . go $ n - 1
59+
60+ trivialSegmentation :: [Cluster] -> Map (Int, Int) [Cluster]
61+ trivialSegmentation cs' = Map.fromList [(bounds points, cs')]
62+
63+dictIn :: SerializableDict ((Int, Int), [Cluster])
64+dictIn = SerializableDict
65+
66+dictOut :: SerializableDict [(Cluster, Point)]
67+dictOut = SerializableDict
68+
69+remotable ['kmeans, 'dictIn, 'dictOut]
70+
71+distrKMeans :: Array Int Point
72+ -> [Cluster]
73+ -> [NodeId]
74+ -> Int
75+ -> Process (Map Cluster ([Point], Point))
76+distrKMeans points cs mappers iterations =
77+ distrMapReduce $(mkStatic 'dictIn)
78+ $(mkStatic 'dictOut)
79+ ($(mkClosure 'kmeans) points)
80+ mappers
81+ (go (iterations - 1))
82+ where
83+ go :: Int
84+ -> (Map (Int, Int) [Cluster] -> Process (Map Cluster ([Point], Point)))
85+ -> Process (Map Cluster ([Point], Point))
86+ go 0 iteration =
87+ iteration (Map.fromList $ map (, cs) segments)
88+ go n iteration = do
89+ clusters <- go (n - 1) iteration
90+ let centers = map snd $ Map.elems clusters
91+ iteration (Map.fromList $ map (, centers) segments)
92+
93+ segments :: [(Int, Int)]
94+ segments = let (lo, _) = bounds points in dividePoints numPoints lo
95+
96+ dividePoints :: Int -> Int -> [(Int, Int)]
97+ dividePoints pointsLeft offset
98+ | pointsLeft <= pointsPerMapper = [(offset, offset + pointsLeft - 1)]
99+ | otherwise = let offset' = offset + pointsPerMapper in
100+ (offset, offset' - 1)
101+ : dividePoints (pointsLeft - pointsPerMapper) offset'
102+
103+ pointsPerMapper :: Int
104+ pointsPerMapper =
105+ ceiling (toRational numPoints / toRational (length mappers))
106+
107+ numPoints :: Int
108+ numPoints = let (lo, hi) = bounds points in hi - lo + 1
109+
110+-- | Create a gnuplot data file for the output of the k-means algorithm
111+--
112+-- To plot the data, use
113+--
114+-- > plot "<<filename>>" u 1:2:3 with points palette
115+createGnuPlot :: Map KMeans.Cluster ([KMeans.Point], KMeans.Point) -> Handle -> IO ()
116+createGnuPlot clusters h =
117+ mapM_ printPoint . flatten . zip colors . Map.toList $ clusters
118+ where
119+ printPoint (x, y, color) =
120+ hPutStrLn h $ show x ++ " " ++ show y ++ " " ++ show color
121+
122+ flatten :: [(Float, (KMeans.Cluster, ([KMeans.Point], KMeans.Point)))]
123+ -> [(Double, Double, Float)]
124+ flatten = concatMap (\(color, (_, (points, _))) -> map (\(x, y) -> (x, y, color)) points)
125+
126+ colors :: [Float]
127+ colors = [0, 1 / fromIntegral (Map.size clusters) .. 1]
distributed-process-demos/src/MapReduce/MapReduce.hsnew file+46
@@ -0,0 +1,46 @@
1+{-# LANGUAGE ScopedTypeVariables, DeriveDataTypeable, GADTs #-}
2+module MapReduce
3+ ( -- * Map-reduce skeleton and implementation
4+ MapReduce(..)
5+ , localMapReduce
6+ -- * Map-reduce algorithmic components
7+ , reducePerKey
8+ , groupByKey
9+ -- * Re-exports from Data.Map
10+ , Map
11+ ) where
12+
13+import Data.Typeable (Typeable)
14+import Data.Map (Map)
15+import qualified Data.Map as Map (mapWithKey, fromListWith, toList)
16+import Control.Arrow (second)
17+
18+--------------------------------------------------------------------------------
19+-- Definition of a MapReduce skeleton and a local implementation --
20+--------------------------------------------------------------------------------
21+
22+-- | MapReduce skeleton
23+data MapReduce k1 v1 k2 v2 v3 = MapReduce {
24+ mrMap :: k1 -> v1 -> [(k2, v2)]
25+ , mrReduce :: k2 -> [v2] -> v3
26+ } deriving (Typeable)
27+
28+-- | Local (non-distributed) implementation of the map-reduce algorithm
29+--
30+-- This can be regarded as the specification of map-reduce; see
31+-- /Google’s MapReduce Programming Model---Revisited/ by Ralf Laemmel
32+-- (<http://userpages.uni-koblenz.de/~laemmel/MapReduce/>).
33+localMapReduce :: forall k1 k2 v1 v2 v3. Ord k2 =>
34+ MapReduce k1 v1 k2 v2 v3
35+ -> Map k1 v1
36+ -> Map k2 v3
37+localMapReduce mr = reducePerKey mr . groupByKey . mapPerKey mr
38+
39+reducePerKey :: MapReduce k1 v1 k2 v2 v3 -> Map k2 [v2] -> Map k2 v3
40+reducePerKey mr = Map.mapWithKey (mrReduce mr)
41+
42+groupByKey :: Ord k2 => [(k2, v2)] -> Map k2 [v2]
43+groupByKey = Map.fromListWith (++) . map (second return)
44+
45+mapPerKey :: MapReduce k1 v1 k2 v2 v3 -> Map k1 v1 -> [(k2, v2)]
46+mapPerKey mr = concatMap (uncurry (mrMap mr)) . Map.toList
distributed-process-demos/src/MapReduce/MonoDistrMapReduce.hsnew file+60
@@ -0,0 +1,60 @@
1+-- | Monomorphic "single-shot" distributed implementation of map-reduce
2+module MonoDistrMapReduce (distrMapReduce, __remoteTable) where
3+
4+import Data.Map (Map)
5+import qualified Data.Map as Map (size, toList)
6+import Control.Monad (forM_, replicateM, replicateM_)
7+import Control.Distributed.Process
8+import Control.Distributed.Process.Closure
9+import MapReduce (MapReduce(..), reducePerKey, groupByKey)
10+
11+--------------------------------------------------------------------------------
12+-- Simple distributed implementation --
13+--------------------------------------------------------------------------------
14+
15+mapperProcess :: (ProcessId, ProcessId, Closure (MapReduce String String String Int Int))
16+ -> Process ()
17+mapperProcess (master, workQueue, mrClosure) = do
18+ us <- getSelfPid
19+ mr <- unClosure mrClosure
20+ go us mr
21+ where
22+ go us mr = do
23+ -- Ask the queue for work
24+ send workQueue us
25+
26+ -- Wait for a reply; if there is work, do it and repeat; otherwise, exit
27+ receiveWait
28+ [ match $ \(key, val) -> send master (mrMap mr key val) >> go us mr
29+ , match $ \() -> return ()
30+ ]
31+
32+remotable ['mapperProcess]
33+
34+distrMapReduce :: Closure (MapReduce String String String Int Int)
35+ -> [NodeId]
36+ -> Map String String
37+ -> Process (Map String Int)
38+distrMapReduce mrClosure mappers input = do
39+ mr <- unClosure mrClosure
40+ master <- getSelfPid
41+
42+ workQueue <- spawnLocal $ do
43+ -- Return the next bit of work to be done
44+ forM_ (Map.toList input) $ \(key, val) -> do
45+ them <- expect
46+ send them (key, val)
47+
48+ -- Once all teh work is done tell the mappers to terminate
49+ replicateM_ (length mappers) $ do
50+ them <- expect
51+ send them ()
52+
53+ -- Start the mappers
54+ forM_ mappers $ \nid -> spawn nid ($(mkClosure 'mapperProcess) (master, workQueue, mrClosure))
55+
56+ -- Wait for the partial results
57+ partials <- replicateM (Map.size input) expect
58+
59+ -- We reduce on this node
60+ return (reducePerKey mr . groupByKey . concat $ partials)
distributed-process-demos/src/MapReduce/PolyDistrMapReduce.hsnew file+123
@@ -0,0 +1,123 @@
1+-- | Polymorphic "multi-shot" distributed implementation of map-reduce
2+module PolyDistrMapReduce (distrMapReduce, __remoteTable) where
3+
4+import Data.Typeable (Typeable)
5+import Data.Binary (encode)
6+import Data.ByteString.Lazy (ByteString)
7+import Data.Map (Map)
8+import qualified Data.Map as Map (size, toList)
9+import Control.Monad (forM_, replicateM, replicateM_)
10+import Control.Distributed.Process
11+import Control.Distributed.Process.Serializable (Serializable)
12+import Control.Distributed.Process.Closure
13+import Control.Distributed.Static (closureApply, staticCompose, staticApply)
14+import MapReduce (MapReduce(..), reducePerKey, groupByKey)
15+
16+--------------------------------------------------------------------------------
17+-- Simple distributed implementation --
18+--------------------------------------------------------------------------------
19+
20+matchDict :: forall a b. SerializableDict a -> (a -> Process b) -> Match b
21+matchDict SerializableDict = match
22+
23+sendDict :: forall a. SerializableDict a -> ProcessId -> a -> Process ()
24+sendDict SerializableDict = send
25+
26+sdictProcessIdPair :: SerializableDict (ProcessId, ProcessId)
27+sdictProcessIdPair = SerializableDict
28+
29+mapperProcess :: forall k1 v1 k2 v2 v3.
30+ SerializableDict (k1, v1)
31+ -> SerializableDict [(k2, v2)]
32+ -> (ProcessId, ProcessId)
33+ -> MapReduce k1 v1 k2 v2 v3
34+ -> Process ()
35+mapperProcess dictIn dictOut (master, workQueue) mr = getSelfPid >>= go
36+ where
37+ go us = do
38+ -- Ask the queue for work
39+ send workQueue us
40+
41+ -- Wait for a reply; if there is work, do it and repeat; otherwise, exit
42+ receiveWait
43+ [ matchDict dictIn $ \(key, val) -> do
44+ sendDict dictOut master (mrMap mr key val)
45+ go us
46+ , match $ \() ->
47+ return ()
48+ ]
49+
50+remotable ['mapperProcess, 'sdictProcessIdPair]
51+
52+mapperProcessClosure :: forall k1 v1 k2 v2 v3.
53+ (Typeable k1, Typeable v1, Typeable k2, Typeable v2, Typeable v3)
54+ => Static (SerializableDict (k1, v1))
55+ -> Static (SerializableDict [(k2, v2)])
56+ -> Closure (MapReduce k1 v1 k2 v2 v3)
57+ -> ProcessId
58+ -> ProcessId
59+ -> Closure (Process ())
60+mapperProcessClosure dictIn dictOut mr master workQueue =
61+ closure decoder (encode (master, workQueue)) `closureApply` mr
62+ where
63+ decoder :: Static (ByteString -> MapReduce k1 v1 k2 v2 v3 -> Process ())
64+ decoder =
65+ ($(mkStatic 'mapperProcess) `staticApply` dictIn `staticApply` dictOut)
66+ `staticCompose`
67+ staticDecode $(mkStatic 'sdictProcessIdPair)
68+
69+distrMapReduce :: forall k1 k2 v1 v2 v3 a.
70+ (Serializable k1, Serializable v1, Serializable k2, Serializable v2, Serializable v3, Ord k2)
71+ => Static (SerializableDict (k1, v1))
72+ -> Static (SerializableDict [(k2, v2)])
73+ -> Closure (MapReduce k1 v1 k2 v2 v3)
74+ -> [NodeId]
75+ -> ((Map k1 v1 -> Process (Map k2 v3)) -> Process a)
76+ -> Process a
77+distrMapReduce dictIn dictOut mr mappers p = do
78+ mr' <- unClosure mr
79+ master <- getSelfPid
80+
81+ workQueue <- spawnChannelLocal $ \queue -> do
82+ let go :: Process ()
83+ go = do
84+ mWork <- receiveChan queue
85+ case mWork of
86+ Just (key, val) -> do
87+ -- As long there is work, make it available to the mappers
88+ them <- expect
89+ send them (key, val)
90+ go
91+ Nothing -> do
92+ -- Tell the mappers to terminate
93+ replicateM_ (length mappers) $ do
94+ them <- expect
95+ send them ()
96+
97+ -- Tell the master that the slaves are terminated
98+ send master ()
99+ go
100+
101+ -- Start the mappers
102+ let workQueuePid = sendPortProcessId (sendPortId workQueue)
103+ forM_ mappers $ \nid ->
104+ spawn nid (mapperProcessClosure dictIn dictOut mr master workQueuePid)
105+
106+ let iteration :: Map k1 v1 -> Process (Map k2 v3)
107+ iteration input = do
108+ -- Make work available to the mappers
109+ mapM_ (sendChan workQueue . Just) (Map.toList input)
110+
111+ -- Wait for the partial results
112+ partials <- replicateM (Map.size input) expect
113+
114+ -- We reduce on this node
115+ return (reducePerKey mr' . groupByKey . concat $ partials)
116+
117+ result <- p iteration
118+
119+ -- Terminate the wrappers
120+ sendChan workQueue Nothing
121+ expect :: Process ()
122+
123+ return result
distributed-process-demos/src/MapReduce/SimpleLocalnet.hsnew file+75
@@ -0,0 +1,75 @@
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 PolyDistrMapReduce
15+import qualified MonoDistrMapReduce
16+import qualified KMeans
17+
18+rtable :: RemoteTable
19+rtable = PolyDistrMapReduce.__remoteTable
20+ . MonoDistrMapReduce.__remoteTable
21+ . CountWords.__remoteTable
22+ . KMeans.__remoteTable
23+ $ initRemoteTable
24+
25+main :: IO ()
26+main = do
27+ args <- getArgs
28+
29+ case args of
30+ -- Local word count
31+ "local" : "count" : files -> do
32+ input <- constructInput files
33+ print $ CountWords.localCountWords input
34+
35+ -- Distributed word count
36+ "master" : host : port : "count" : files -> do
37+ input <- constructInput files
38+ backend <- initializeBackend host port rtable
39+ startMaster backend $ \slaves -> do
40+ result <- CountWords.distrCountWords slaves input
41+ liftIO $ print result
42+
43+ -- Local k-means
44+ "local" : "kmeans" : [] -> do
45+ points <- replicateM 50000 randomPoint
46+ withFile "plot.data" WriteMode $ KMeans.createGnuPlot $
47+ KMeans.localKMeans (arrayFromList points) (take 5 points) 5
48+
49+ -- Distributed k-means
50+ "master" : host : port : "kmeans" : [] -> do
51+ points <- replicateM 50000 randomPoint
52+ backend <- initializeBackend host port rtable
53+ startMaster backend $ \slaves -> do
54+ result <- KMeans.distrKMeans (arrayFromList points) (take 5 points) slaves 5
55+ liftIO $ withFile "plot.data" WriteMode $ KMeans.createGnuPlot result
56+
57+ -- Generic slave for distributed examples
58+ "slave" : host : port : [] -> do
59+ backend <- initializeBackend host port rtable
60+ startSlave backend
61+
62+--------------------------------------------------------------------------------
63+-- Auxiliary --
64+--------------------------------------------------------------------------------
65+
66+constructInput :: [FilePath] -> IO (Map FilePath CountWords.Document)
67+constructInput files = do
68+ contents <- mapM readFile files
69+ return . Map.fromList $ zip files contents
70+
71+randomPoint :: IO KMeans.Point
72+randomPoint = (,) <$> randomIO <*> randomIO
73+
74+arrayFromList :: [e] -> Array Int e
75+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+49
@@ -0,0 +1,49 @@
1+module MasterSlave where
2+
3+import Control.Monad
4+import Control.Distributed.Process
5+import Control.Distributed.Process.Closure
6+import PrimeFactors
7+
8+slave :: (ProcessId, Integer) -> Process ()
9+slave (pid, n) = send pid (numPrimeFactors n)
10+
11+remotable ['slave]
12+
13+-- | Wait for n integers and sum them all up
14+sumIntegers :: Int -> Process Integer
15+sumIntegers = go 0
16+ where
17+ go :: Integer -> Int -> Process Integer
18+ go !acc 0 = return acc
19+ go !acc n = do
20+ m <- expect
21+ go (acc + m) (n - 1)
22+
23+data SpawnStrategy = SpawnSyncWithReconnect
24+ | SpawnSyncNoReconnect
25+ | SpawnAsync
26+ deriving (Show, Read)
27+
28+master :: Integer -> SpawnStrategy -> [NodeId] -> Process Integer
29+master n spawnStrategy slaves = do
30+ us <- getSelfPid
31+
32+ -- Distribute 1 .. n amongst the slave processes
33+ spawnLocal $ case spawnStrategy of
34+ SpawnSyncWithReconnect ->
35+ forM_ (zip [1 .. n] (cycle slaves)) $ \(m, there) -> do
36+ them <- spawn there ($(mkClosure 'slave) (us, m))
37+ reconnect them
38+ SpawnSyncNoReconnect ->
39+ forM_ (zip [1 .. n] (cycle slaves)) $ \(m, there) -> do
40+ _them <- spawn there ($(mkClosure 'slave) (us, m))
41+ return ()
42+ SpawnAsync ->
43+ forM_ (zip [1 .. n] (cycle slaves)) $ \(m, there) -> do
44+ spawnAsync there ($(mkClosure 'slave) (us, m))
45+ _ <- expectTimeout 0 :: Process (Maybe DidSpawn)
46+ return ()
47+
48+ -- Wait for the result
49+ sumIntegers (fromIntegral n)
distributed-process-demos/src/MasterSlave/SimpleLocalnet.hsnew file+25
@@ -0,0 +1,25 @@
1+import System.Environment (getArgs)
2+import Control.Exception (evaluate)
3+import Control.Distributed.Process
4+import Control.Distributed.Process.Node (initRemoteTable)
5+import Control.Distributed.Process.Backend.SimpleLocalnet
6+import qualified MasterSlave
7+
8+rtable :: RemoteTable
9+rtable = MasterSlave.__remoteTable initRemoteTable
10+
11+main :: IO ()
12+main = do
13+ args <- getArgs
14+
15+ case args of
16+ ["master", host, port, strN, strSpawnStrategy] -> do
17+ backend <- initializeBackend host port rtable
18+ n <- evaluate $ read strN
19+ spawnStrategy <- evaluate $ read strSpawnStrategy
20+ startMaster backend $ \slaves -> do
21+ result <- MasterSlave.master n spawnStrategy slaves
22+ liftIO $ print result
23+ ["slave", host, port] -> do
24+ backend <- initializeBackend host port rtable
25+ startSlave backend
distributed-process-demos/src/TypedWorkPushing/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/TypedWorkPushing/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 TypedWorkPushing
6+
7+rtable :: RemoteTable
8+rtable = TypedWorkPushing.__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 <- TypedWorkPushing.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/TypedWorkPushing/TypedWorkPushing.hsnew file+41
@@ -0,0 +1,41 @@
1+module TypedWorkPushing where
2+
3+import Control.Monad
4+import Control.Distributed.Process
5+import Control.Distributed.Process.Closure
6+import PrimeFactors
7+
8+slave :: SendPort Integer -> ReceivePort Integer -> Process ()
9+slave results todo = forever $ do
10+ n <- receiveChan todo
11+ sendChan results (numPrimeFactors n)
12+
13+sdictInteger :: SerializableDict Integer
14+sdictInteger = SerializableDict
15+
16+remotable ['slave, 'sdictInteger]
17+
18+-- | Wait for n integers and sum them all up
19+sumIntegers :: ReceivePort Integer -> Int -> Process Integer
20+sumIntegers rport = go 0
21+ where
22+ go :: Integer -> Int -> Process Integer
23+ go !acc 0 = return acc
24+ go !acc n = do
25+ m <- receiveChan rport
26+ go (acc + m) (n - 1)
27+
28+master :: Integer -> [NodeId] -> Process Integer
29+master n slaves = do
30+ (sport, rport) <- newChan
31+
32+ -- Start slave processes
33+ slaveProcesses <- forM slaves $ \nid ->
34+ spawnChannel $(mkStatic 'sdictInteger) nid ($(mkClosure 'slave) sport)
35+
36+ -- Distribute 1 .. n amongst the slave processes
37+ spawnLocal $ forM_ (zip [1 .. n] (cycle slaveProcesses)) $
38+ \(m, them) -> sendChan them m
39+
40+ -- Wait for the result
41+ sumIntegers rport (fromIntegral n)
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+37
@@ -0,0 +1,37 @@
1+module WorkPushing where
2+
3+import Control.Monad
4+import Control.Distributed.Process
5+import Control.Distributed.Process.Closure
6+import PrimeFactors
7+
8+slave :: ProcessId -> Process ()
9+slave them = forever $ do
10+ n <- expect
11+ send them (numPrimeFactors n)
12+
13+remotable ['slave]
14+
15+-- | Wait for n integers and sum them all up
16+sumIntegers :: Int -> Process Integer
17+sumIntegers = go 0
18+ where
19+ go :: Integer -> Int -> Process Integer
20+ go !acc 0 = return acc
21+ go !acc n = do
22+ m <- expect
23+ go (acc + m) (n - 1)
24+
25+master :: Integer -> [NodeId] -> Process Integer
26+master n slaves = do
27+ us <- getSelfPid
28+
29+ -- Start slave processes
30+ slaveProcesses <- forM slaves $ \nid -> spawn nid ($(mkClosure 'slave) us)
31+
32+ -- Distribute 1 .. n amongst the slave processes
33+ spawnLocal $ forM_ (zip [1 .. n] (cycle slaveProcesses)) $
34+ \(m, them) -> send them m
35+
36+ -- Wait for the result
37+ sumIntegers (fromIntegral n)
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+54
@@ -0,0 +1,54 @@
1+module WorkStealing where
2+
3+import Control.Monad
4+import Control.Distributed.Process
5+import Control.Distributed.Process.Closure
6+import PrimeFactors
7+
8+slave :: (ProcessId, ProcessId) -> Process ()
9+slave (master, workQueue) = do
10+ us <- getSelfPid
11+ go us
12+ where
13+ go us = do
14+ -- Ask the queue for work
15+ send workQueue us
16+
17+ -- If there is work, do it, otherwise terminate
18+ receiveWait
19+ [ match $ \n -> send master (numPrimeFactors n) >> go us
20+ , match $ \() -> return ()
21+ ]
22+
23+remotable ['slave]
24+
25+-- | Wait for n integers and sum them all up
26+sumIntegers :: Int -> Process Integer
27+sumIntegers = go 0
28+ where
29+ go :: Integer -> Int -> Process Integer
30+ go !acc 0 = return acc
31+ go !acc n = do
32+ m <- expect
33+ go (acc + m) (n - 1)
34+
35+master :: Integer -> [NodeId] -> Process Integer
36+master n slaves = do
37+ us <- getSelfPid
38+
39+ workQueue <- spawnLocal $ do
40+ -- Reply with the next bit of work to be done
41+ forM_ [1 .. n] $ \m -> do
42+ them <- expect
43+ send them m
44+
45+ -- Once all the work is done, tell the slaves to terminate
46+ forever $ do
47+ pid <- expect
48+ send pid ()
49+
50+ -- Start slave processes
51+ forM_ slaves $ \nid -> spawn nid ($(mkClosure 'slave) (us, workQueue))
52+
53+ -- Wait for the result
54+ sumIntegers (fromIntegral n)
distributed-process-simplelocalnet/ChangeLognew file+39
@@ -0,0 +1,39 @@
1+2012-11-22 Edsko de Vries <edsko@well-typed.com> 0.2.0.8
2+
3+* Use the new 'register' semantics (depends on distributed-process-0.4.1).
4+Patch by Jeff Epstein
5+* Relax package bounds to allow for Binary 0.6
6+
7+2012-10-23 Edsko de Vries <edsko@well-typed.com> 0.2.0.7
8+
9+* Fix cabal script so that the example program compiles
10+
11+2012-10-03 Edsko de Vries <edsko@well-typed.com> 0.2.0.6
12+
13+* Use new version of network-transport
14+* network-2.4.0 compatibility
15+* Relax upper bound on distributed-process dependency
16+
17+2012-08-22 Edsko de Vries <edsko@well-typed.com> 0.2.0.5
18+
19+* Don't assume slaves are still alive in findSlaves
20+
21+2012-08-09 Edsko de Vries <edsko@well-typed.com> 0.2.0.4
22+
23+* Relax version bounds for distributed-process
24+
25+2012-07-17 Edsko de Vries <edsko@well-typed.com> 0.2.0.3
26+
27+* Improve documentation
28+
29+2012-07-16 Edsko de Vries <edsko@well-typed.com> 0.2.0.2
30+
31+* Relax contraints on bytestring and containers
32+
33+2012-07-09 Edsko de Vries <edsko@well-typed.com> 0.2.0.1
34+
35+* Bugfix: Documentation referred to old module name
36+
37+2012-07-07 Edsko de Vries <edsko@well-typed.com> 0.2.0
38+
39+* 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+70
@@ -0,0 +1,70 @@
1+Name: distributed-process-simplelocalnet
2+Version: 0.2.0.8
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, duncan@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+Flag build-example
27+ Default: False
28+ Description: Build a simple example application
29+
30+Library
31+ Build-Depends: base >= 4.4 && < 5,
32+ bytestring >= 0.9 && < 0.11,
33+ network >= 2.3 && < 2.5,
34+ network-multicast >= 0.0 && < 0.1,
35+ data-accessor >= 0.2 && < 0.3,
36+ binary >= 0.5 && < 0.7,
37+ containers >= 0.4 && < 0.6,
38+ transformers >= 0.2 && < 0.4,
39+ network-transport >= 0.3 && < 0.4,
40+ network-transport-tcp >= 0.3 && < 0.4,
41+ distributed-process >= 0.4.1 && < 0.5
42+ Exposed-modules: Control.Distributed.Process.Backend.SimpleLocalnet,
43+ Control.Distributed.Process.Backend.SimpleLocalnet.Internal.Multicast
44+ Extensions: RankNTypes,
45+ DeriveDataTypeable,
46+ CPP
47+ ghc-options: -Wall
48+ HS-Source-Dirs: src
49+
50+Executable TestSimpleLocalnet
51+ Main-Is: TestSimpleLocalnet.hs
52+ If flag(build-example)
53+ Build-Depends: base >= 4.4 && < 5,
54+ bytestring >= 0.9 && < 0.11,
55+ network >= 2.3 && < 2.5,
56+ network-multicast >= 0.0 && < 0.1,
57+ data-accessor >= 0.2 && < 0.3,
58+ binary >= 0.5 && < 0.7,
59+ containers >= 0.4 && < 0.6,
60+ transformers >= 0.2 && < 0.4,
61+ network-transport >= 0.3 && < 0.4,
62+ network-transport-tcp >= 0.3 && < 0.4,
63+ distributed-process >= 0.4.1 && < 0.5
64+ Else
65+ Buildable: False
66+ Extensions: RankNTypes,
67+ DeriveDataTypeable,
68+ CPP
69+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
70+ HS-Source-Dirs: tests src
distributed-process-simplelocalnet/src/Control/Distributed/Process/Backend/SimpleLocalnet.hsnew file+420
@@ -0,0 +1,420 @@
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+--
77+-- [Troubleshooting]
78+--
79+-- If you try the above example and the master process cannot find any slaves,
80+-- then it might be that your firewall settings do not allow for UDP multicast
81+-- (in particular, the default iptables on some Linux distributions might not
82+-- allow it).
83+{-# OPTIONS_GHC -fno-warn-orphans #-}
84+module Control.Distributed.Process.Backend.SimpleLocalnet
85+ ( -- * Initialization
86+ Backend(..)
87+ , initializeBackend
88+ -- * Slave nodes
89+ , startSlave
90+ , terminateSlave
91+ , findSlaves
92+ , terminateAllSlaves
93+ -- * Master nodes
94+ , startMaster
95+ ) where
96+
97+import System.IO (fixIO)
98+import Data.Maybe (catMaybes)
99+import Data.Binary (Binary(get, put), getWord8, putWord8)
100+import Data.Accessor (Accessor, accessor, (^:), (^.))
101+import Data.Set (Set)
102+import qualified Data.Set as Set (insert, empty, toList)
103+import Data.Foldable (forM_)
104+import Data.Typeable (Typeable)
105+import Control.Applicative ((<$>))
106+import Control.Exception (throw)
107+import Control.Monad (forever, replicateM, replicateM_)
108+import Control.Monad.IO.Class (liftIO)
109+import Control.Concurrent (forkIO, threadDelay, ThreadId)
110+import Control.Concurrent.MVar (MVar, newMVar, readMVar, modifyMVar_)
111+import Control.Distributed.Process
112+ ( RemoteTable
113+ , NodeId
114+ , Process
115+ , ProcessId
116+ , WhereIsReply(..)
117+ , whereis
118+ , whereisRemoteAsync
119+ , getSelfPid
120+ , register
121+ , reregister
122+ , expect
123+ , nsendRemote
124+ , receiveWait
125+ , match
126+ , processNodeId
127+ , monitorNode
128+ , monitor
129+ , unmonitor
130+ , NodeMonitorNotification(..)
131+ , ProcessRegistrationException
132+ , finally
133+ , newChan
134+ , receiveChan
135+ , nsend
136+ , SendPort
137+ , bracket
138+ , try
139+ , send
140+ )
141+import qualified Control.Distributed.Process.Node as Node
142+ ( LocalNode
143+ , newLocalNode
144+ , localNodeId
145+ , runProcess
146+ )
147+import qualified Network.Transport.TCP as NT
148+ ( createTransport
149+ , defaultTCPParameters
150+ )
151+import qualified Network.Transport as NT (Transport)
152+import qualified Network.Socket as N (HostName, ServiceName, SockAddr)
153+import Control.Distributed.Process.Backend.SimpleLocalnet.Internal.Multicast (initMulticast)
154+
155+-- | Local backend
156+data Backend = Backend {
157+ -- | Create a new local node
158+ newLocalNode :: IO Node.LocalNode
159+ -- | @findPeers t@ broadcasts a /who's there?/ message on the local
160+ -- network, waits 't' msec, and then collects and returns the answers.
161+ -- You can use this to dynamically discover peer nodes.
162+ , findPeers :: Int -> IO [NodeId]
163+ -- | Make sure that all log messages are printed by the logger on the
164+ -- current node
165+ , redirectLogsHere :: [ProcessId] -> Process ()
166+ }
167+
168+data BackendState = BackendState {
169+ _localNodes :: [Node.LocalNode]
170+ , _peers :: Set NodeId
171+ , discoveryDaemon :: ThreadId
172+ }
173+
174+-- | Initialize the backend
175+initializeBackend :: N.HostName -> N.ServiceName -> RemoteTable -> IO Backend
176+initializeBackend host port rtable = do
177+ mTransport <- NT.createTransport host port NT.defaultTCPParameters
178+ (recv, sendp) <- initMulticast "224.0.0.99" 9999 1024
179+ (_, backendState) <- fixIO $ \ ~(tid, _) -> do
180+ backendState <- newMVar BackendState
181+ { _localNodes = []
182+ , _peers = Set.empty
183+ , discoveryDaemon = tid
184+ }
185+ tid' <- forkIO $ peerDiscoveryDaemon backendState recv sendp
186+ return (tid', backendState)
187+ case mTransport of
188+ Left err -> throw err
189+ Right transport ->
190+ let backend = Backend {
191+ newLocalNode = apiNewLocalNode transport rtable backendState
192+ , findPeers = apiFindPeers sendp backendState
193+ , redirectLogsHere = apiRedirectLogsHere backend
194+ }
195+ in return backend
196+
197+-- | Create a new local node
198+apiNewLocalNode :: NT.Transport
199+ -> RemoteTable
200+ -> MVar BackendState
201+ -> IO Node.LocalNode
202+apiNewLocalNode transport rtable backendState = do
203+ localNode <- Node.newLocalNode transport rtable
204+ modifyMVar_ backendState $ return . (localNodes ^: (localNode :))
205+ return localNode
206+
207+-- | Peer discovery
208+apiFindPeers :: (PeerDiscoveryMsg -> IO ())
209+ -> MVar BackendState
210+ -> Int
211+ -> IO [NodeId]
212+apiFindPeers sendfn backendState delay = do
213+ sendfn PeerDiscoveryRequest
214+ threadDelay delay
215+ Set.toList . (^. peers) <$> readMVar backendState
216+
217+data PeerDiscoveryMsg =
218+ PeerDiscoveryRequest
219+ | PeerDiscoveryReply NodeId
220+
221+instance Binary PeerDiscoveryMsg where
222+ put PeerDiscoveryRequest = putWord8 0
223+ put (PeerDiscoveryReply nid) = putWord8 1 >> put nid
224+ get = do
225+ header <- getWord8
226+ case header of
227+ 0 -> return PeerDiscoveryRequest
228+ 1 -> PeerDiscoveryReply <$> get
229+ _ -> fail "PeerDiscoveryMsg.get: invalid"
230+
231+-- | Respond to peer discovery requests sent by other nodes
232+peerDiscoveryDaemon :: MVar BackendState
233+ -> IO (PeerDiscoveryMsg, N.SockAddr)
234+ -> (PeerDiscoveryMsg -> IO ())
235+ -> IO ()
236+peerDiscoveryDaemon backendState recv sendfn = forever go
237+ where
238+ go = do
239+ (msg, _) <- recv
240+ case msg of
241+ PeerDiscoveryRequest -> do
242+ nodes <- (^. localNodes) <$> readMVar backendState
243+ forM_ nodes $ sendfn . PeerDiscoveryReply . Node.localNodeId
244+ PeerDiscoveryReply nid ->
245+ modifyMVar_ backendState $ return . (peers ^: Set.insert nid)
246+
247+--------------------------------------------------------------------------------
248+-- Back-end specific primitives --
249+--------------------------------------------------------------------------------
250+
251+-- | Make sure that all log messages are printed by the logger on this node
252+apiRedirectLogsHere :: Backend -> [ProcessId] -> Process ()
253+apiRedirectLogsHere _backend slavecontrollers = do
254+ mLogger <- whereis "logger"
255+ myPid <- getSelfPid
256+
257+ forM_ mLogger $ \logger -> do
258+
259+ bracket
260+ (mapM monitor slavecontrollers)
261+ (mapM unmonitor)
262+ $ \_ -> do
263+
264+ -- fire off redirect requests
265+ forM_ slavecontrollers $ \pid -> send pid (RedirectLogsTo logger myPid)
266+
267+ -- Wait for the replies
268+ replicateM_ (length slavecontrollers) $ do
269+ receiveWait
270+ [ match (\(RedirectLogsReply {}) -> return ())
271+ , match (\(NodeMonitorNotification {}) -> return ())
272+ ]
273+
274+--------------------------------------------------------------------------------
275+-- Slaves --
276+--------------------------------------------------------------------------------
277+
278+-- | Messages to slave nodes
279+--
280+-- This datatype is not exposed; instead, we expose primitives for dealing
281+-- with slaves.
282+data SlaveControllerMsg
283+ = SlaveTerminate
284+ | RedirectLogsTo ProcessId ProcessId
285+ deriving (Typeable, Show)
286+
287+instance Binary SlaveControllerMsg where
288+ put SlaveTerminate = putWord8 0
289+ put (RedirectLogsTo a b) = do putWord8 1; put (a,b)
290+ get = do
291+ header <- getWord8
292+ case header of
293+ 0 -> return SlaveTerminate
294+ 1 -> do (a,b) <- get; return (RedirectLogsTo a b)
295+ _ -> fail "SlaveControllerMsg.get: invalid"
296+
297+data RedirectLogsReply
298+ = RedirectLogsReply ProcessId Bool
299+ deriving (Typeable, Show)
300+
301+instance Binary RedirectLogsReply where
302+ put (RedirectLogsReply from ok) = put (from,ok)
303+ get = do
304+ (from,ok) <- get
305+ return (RedirectLogsReply from ok)
306+
307+-- | Calling 'slave' sets up a new local node and then waits. You start
308+-- processes on the slave by calling 'spawn' from other nodes.
309+--
310+-- This function does not return. The only way to exit the slave is to CTRL-C
311+-- the process or call terminateSlave from another node.
312+startSlave :: Backend -> IO ()
313+startSlave backend = do
314+ node <- newLocalNode backend
315+ Node.runProcess node slaveController
316+
317+-- | The slave controller interprets 'SlaveControllerMsg's
318+slaveController :: Process ()
319+slaveController = do
320+ pid <- getSelfPid
321+ register "slaveController" pid
322+ go
323+ where
324+ go = do
325+ msg <- expect
326+ case msg of
327+ SlaveTerminate -> return ()
328+ RedirectLogsTo loggerPid from -> do
329+ r <- try (reregister "logger" loggerPid)
330+ ok <- case (r :: Either ProcessRegistrationException ()) of
331+ Right _ -> return True
332+ Left _ -> do
333+ s <- try (register "logger" loggerPid)
334+ case (s :: Either ProcessRegistrationException ()) of
335+ Right _ -> return True
336+ Left _ -> return False
337+ pid <- getSelfPid
338+ send from (RedirectLogsReply pid ok)
339+ go
340+
341+-- | Terminate the slave at the given node ID
342+terminateSlave :: NodeId -> Process ()
343+terminateSlave nid = nsendRemote nid "slaveController" SlaveTerminate
344+
345+-- | Find slave nodes
346+findSlaves :: Backend -> Process [ProcessId]
347+findSlaves backend = do
348+ nodes <- liftIO $ findPeers backend 1000000
349+ -- Fire off asynchronous requests for the slave controller
350+
351+ bracket
352+ (mapM monitorNode nodes)
353+ (mapM unmonitor)
354+ $ \_ -> do
355+
356+ -- fire off whereis requests
357+ forM_ nodes $ \nid -> whereisRemoteAsync nid "slaveController"
358+
359+ -- Wait for the replies
360+ catMaybes <$> replicateM (length nodes) (
361+ receiveWait
362+ [ match (\(WhereIsReply "slaveController" mPid) -> return mPid)
363+ , match (\(NodeMonitorNotification {}) -> return Nothing)
364+ ])
365+
366+-- | Terminate all slaves
367+terminateAllSlaves :: Backend -> Process ()
368+terminateAllSlaves backend = do
369+ slaves <- findSlaves backend
370+ forM_ slaves $ \pid -> send pid SlaveTerminate
371+ liftIO $ threadDelay 1000000
372+
373+--------------------------------------------------------------------------------
374+-- Master nodes
375+--------------------------------------------------------------------------------
376+
377+-- | 'startMaster' finds all slaves /currently/ available on the local network,
378+-- redirects all log messages to itself, and then calls the specified process,
379+-- passing the list of slaves nodes.
380+--
381+-- Terminates when the specified process terminates. If you want to terminate
382+-- the slaves when the master terminates, you should manually call
383+-- 'terminateAllSlaves'.
384+--
385+-- If you start more slave nodes after having started the master node, you can
386+-- discover them with later calls to 'findSlaves', but be aware that you will
387+-- need to call 'redirectLogHere' to redirect their logs to the master node.
388+--
389+-- Note that you can use functionality of "SimpleLocalnet" directly (through
390+-- 'Backend'), instead of using 'startMaster'/'startSlave', if the master/slave
391+-- distinction does not suit your application.
392+startMaster :: Backend -> ([NodeId] -> Process ()) -> IO ()
393+startMaster backend proc = do
394+ node <- newLocalNode backend
395+ Node.runProcess node $ do
396+ slaves <- findSlaves backend
397+ redirectLogsHere backend slaves
398+ proc (map processNodeId slaves) `finally` shutdownLogger
399+
400+--
401+-- | shut down the logger process. This ensures that any pending
402+-- messages are flushed before the process exits.
403+--
404+shutdownLogger :: Process ()
405+shutdownLogger = do
406+ (sport,rport) <- newChan
407+ nsend "logger" (sport :: SendPort ())
408+ receiveChan rport
409+ -- TODO: we should monitor the logger process so we don't deadlock if
410+ -- it has already died.
411+
412+--------------------------------------------------------------------------------
413+-- Accessors --
414+--------------------------------------------------------------------------------
415+
416+localNodes :: Accessor BackendState [Node.LocalNode]
417+localNodes = accessor _localNodes (\ns st -> st { _localNodes = ns })
418+
419+peers :: Accessor BackendState (Set NodeId)
420+peers = accessor _peers (\ps st -> st { _peers = ps })
distributed-process-simplelocalnet/src/Control/Distributed/Process/Backend/SimpleLocalnet/Internal/Multicast.hsnew file+128
@@ -0,0 +1,128 @@
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+#if MIN_VERSION_network(2,4,0)
65+-- network-2.4.0 provides the Ord instance for us
66+#else
67+instance Ord SockAddr where
68+ compare = compare `on` show
69+#endif
70+
71+bufferFor :: SockAddr -> Accessor UDPState BSL.ByteString
72+bufferFor = DAC.mapDefault BSL.empty
73+
74+bufferAppend :: SockAddr -> BSS.ByteString -> UDPState -> UDPState
75+bufferAppend addr bytes =
76+ bufferFor addr ^: flip BSL.append (BSL.fromChunks [bytes])
77+
78+recvBinary :: Binary a => Socket -> IORef UDPState -> Int -> IO (a, SockAddr)
79+recvBinary sock st bufferSize = do
80+ (bytes, addr) <- recvWithLength sock st bufferSize
81+ return (decode bytes, addr)
82+
83+recvWithLength :: Socket
84+ -> IORef UDPState
85+ -> Int
86+ -> IO (BSL.ByteString, SockAddr)
87+recvWithLength sock st bufferSize = do
88+ (len, addr) <- recvExact sock 4 st bufferSize
89+ let n = decodeInt32 . BSS.concat . BSL.toChunks $ len
90+ bytes <- recvExactFrom addr sock n st bufferSize
91+ return (bytes, addr)
92+
93+-- Receive all bytes currently in the buffer
94+recvAll :: Socket -> IORef UDPState -> Int -> IO SockAddr
95+recvAll sock st bufferSize = do
96+ (bytes, addr) <- NBS.recvFrom sock bufferSize
97+ modifyIORef st $ bufferAppend addr bytes
98+ return addr
99+
100+recvExact :: Socket
101+ -> Int
102+ -> IORef UDPState
103+ -> Int
104+ -> IO (BSL.ByteString, SockAddr)
105+recvExact sock n st bufferSize = do
106+ addr <- recvAll sock st bufferSize
107+ bytes <- recvExactFrom addr sock n st bufferSize
108+ return (bytes, addr)
109+
110+recvExactFrom :: SockAddr
111+ -> Socket
112+ -> Int
113+ -> IORef UDPState
114+ -> Int
115+ -> IO BSL.ByteString
116+recvExactFrom addr sock n st bufferSize = go
117+ where
118+ go :: IO BSL.ByteString
119+ go = do
120+ accAddr <- (^. bufferFor addr) <$> readIORef st
121+ if BSL.length accAddr >= fromIntegral n
122+ then do
123+ let (bytes, accAddr') = BSL.splitAt (fromIntegral n) accAddr
124+ modifyIORef st $ bufferFor addr ^= accAddr'
125+ return bytes
126+ else do
127+ _ <- recvAll sock st bufferSize
128+ 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/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+94
@@ -0,0 +1,94 @@
1+
2+* Expose deferred type checking for AbstractMessage
3+* Provide improved exception handling for deferred type checked exit reasons
4+
5+2012-11-22 Edsko de Vries <edsko@well-typed.com> 0.4.1
6+
7+* Make behaviour of 'register' more Erlang-like (register will now fail if the
8+name is already registered). Patch by Jeff Epstein.
9+* Functor, Applicative, Alternative and Monad instances for ReceivePort
10+* Add support for receiveChanTimeout
11+* Improved documentation
12+* Avoid name clashes in the TH generation for closures
13+* Relax package bounds to allow for Binary 0.6
14+
15+2012-10-23 Edsko de Vries <edsko@well-typed.com> 0.4.0.2
16+
17+* Fix race condition in spawn
18+
19+2012-10-04 Edsko de Vries <edsko@well-typed.com> 0.4.0.1
20+
21+* Relax package boundaries
22+
23+2012-10-03 Edsko de Vries <edsko@well-typed.com> 0.4.0
24+
25+* Improved treatment of network failure, using new failure semantics of
26+Network.Transport.
27+* Make NodeId Typeable
28+* Extend Template Haskell support with "remotableDec" so that you can refer to
29+$(mkClosure 'f) within the body of "f".
30+* Fix bug in spawnChannelLocal
31+* Numerous memory leaks plugged
32+* Relax upper bound on dependency on 'network'
33+* New primitive 'matchAny'
34+* Remove 'whereisRemote' (see comment of 'whereisRemoteAsync')
35+
36+2012-08-16 Edsko de Vries <edsko@well-typed.com> 0.3.1
37+
38+* Fix memory leaks
39+* Make Template Haskell support optional
40+* Relax dependency constraints
41+
42+2012-08-07 Edsko de Vries <edsko@well-typed.com> 0.3.0
43+
44+* Extract 'static' into a separate package (C.D.Static)
45+* Use new package rank1dynamic to proper runtime checks for polymorphic values
46+
47+2012-08-02 Edsko de Vries <edsko@well-typed.com> 0.2.3.0
48+
49+* Expose the constructors of Closure
50+* Add instance (Typeable a => Serializable (Static a)) and make sure we only
51+use the internal representation of Static where really necessary
52+* Improved docs
53+
54+2012-07-31 Edsko de Vries <edsko@well-typed.com> 0.2.2.0
55+
56+* Add exception handling primitives
57+* Fix runProcess: if the process threw an exception, a 'waiting indefinitely on
58+MVar' exception would be thrown.
59+
60+2012-07-21 Edsko de Vries <edsko@well-typed.com> 0.2.1.4
61+
62+* Bugfix in the node controller
63+(one way this bug materialized: when using the SimpleLocalnet backend,
64+slave nodes could not be reused)
65+* Improved documentation in Control.Distributed.Process.Closure
66+
67+2012-07-20 Edsko de Vries <edsko@well-typed.com> 0.2.1.3
68+
69+* Improve docs
70+* Local versions of spawn
71+
72+2012-07-16 Edsko de Vries <edsko@well-typed.com> 0.2.1.2
73+
74+* Base 4.6 compatibility
75+* Relax constraints on bytestring and containers
76+
77+2012-07-16 Edsko de Vries <edsko@well-typed.com> 0.2.1.1
78+
79+* Relax upper bound on 'time' dependency
80+
81+2012-07-11 Edsko de Vries <edsko@well-typed.com> 0.2.1
82+
83+* Complete redesign of the underlying implementation of static values and
84+closures.
85+
86+* Add support for 'spawnChannel'
87+
88+2012-07-09 Edsko de Vries <edsko@well-typed.com> 0.2.0.1
89+
90+* Bugfix: Continue processing messages when a connection breaks.
91+
92+2012-07-07 Edsko de Vries <edsko@well-typed.com> 0.2.0
93+
94+* 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+4−6
@@ -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
@@ -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+4−7
@@ -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
@@ -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+4−8
@@ -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,7 +5,7 @@ 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
@@ -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+4−8
@@ -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
@@ -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

The content is the same.

benchmarks/remote/Throughput.hs → distributed-process/benchmarks/remote/Throughput.hsrenamed

The content is the same.

distributed-process/distributed-process.cabalnew file+203
@@ -0,0 +1,203 @@
1+Name: distributed-process
2+Version: 0.4.1
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, duncan@well-typed.com, watson.timothy@gmail.com
10+Stability: experimental
11+Homepage: http://github.com/haskell-distributed/distributed-process
12+Bug-Reports: mailto:edsko@well-typed.com, mailto:watson.timothy@gmail.com, mailto:parallel-haskell@googlegroups.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.7,
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.Trace,
65+ Control.Distributed.Process.Internal.Closure.BuiltIn,
66+ Control.Distributed.Process.Internal.Messaging,
67+ Control.Distributed.Process.Internal.StrictList,
68+ Control.Distributed.Process.Internal.StrictMVar,
69+ Control.Distributed.Process.Internal.WeakTQueue
70+ Control.Distributed.Process.Internal.StrictContainerAccessors
71+ Extensions: RankNTypes,
72+ ScopedTypeVariables,
73+ FlexibleInstances,
74+ UndecidableInstances,
75+ ExistentialQuantification,
76+ GADTs,
77+ GeneralizedNewtypeDeriving,
78+ DeriveDataTypeable,
79+ CPP,
80+ BangPatterns
81+ ghc-options: -Wall
82+ HS-Source-Dirs: src
83+ if flag(th)
84+ Build-Depends: template-haskell >= 2.6 && < 2.9
85+ Exposed-modules: Control.Distributed.Process.Internal.Closure.TH
86+ CPP-Options: -DTemplateHaskellSupport
87+
88+Test-Suite TestCH
89+ Type: exitcode-stdio-1.0
90+ Main-Is: TestCH.hs
91+ Build-Depends: base >= 4.4 && < 5,
92+ random >= 1.0 && < 1.1,
93+ ansi-terminal >= 0.5 && < 0.6,
94+ distributed-process,
95+ network-transport >= 0.3 && < 0.4,
96+ network-transport-tcp >= 0.3 && < 0.4,
97+ binary >= 0.5 && < 0.7,
98+ network >= 2.3 && < 2.5,
99+ HUnit >= 1.2 && < 1.3,
100+ test-framework >= 0.6 && < 0.9,
101+ test-framework-hunit >= 0.2.0 && < 0.4
102+ Extensions: CPP,
103+ ScopedTypeVariables,
104+ DeriveDataTypeable,
105+ GeneralizedNewtypeDeriving
106+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
107+ HS-Source-Dirs: tests
108+
109+Test-Suite TestClosure
110+ Type: exitcode-stdio-1.0
111+ Main-Is: TestClosure.hs
112+ Build-Depends: base >= 4.4 && < 5,
113+ random >= 1.0 && < 1.1,
114+ ansi-terminal >= 0.5 && < 0.6,
115+ distributed-static >= 0.2 && < 0.3,
116+ distributed-process,
117+ network-transport >= 0.3 && < 0.4,
118+ network-transport-tcp >= 0.3 && < 0.4,
119+ bytestring >= 0.9 && < 0.11,
120+ network >= 2.3 && < 2.5,
121+ HUnit >= 1.2 && < 1.3,
122+ test-framework >= 0.6 && < 0.9,
123+ test-framework-hunit >= 0.2.0 && < 0.4
124+ Extensions: CPP,
125+ ScopedTypeVariables
126+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
127+ HS-Source-Dirs: tests
128+
129+Test-Suite TestStats
130+ Type: exitcode-stdio-1.0
131+ Main-Is: TestStats.hs
132+ Build-Depends: base >= 4.4 && < 5,
133+ random >= 1.0 && < 1.1,
134+ ansi-terminal >= 0.5 && < 0.6,
135+ containers >= 0.4 && < 0.6,
136+ stm >= 2.3 && < 2.5,
137+ distributed-process,
138+ network-transport >= 0.3 && < 0.4,
139+ network-transport-tcp >= 0.3 && < 0.4,
140+ binary >= 0.5 && < 0.7,
141+ network >= 2.3 && < 2.5,
142+ HUnit >= 1.2 && < 1.3,
143+ test-framework >= 0.6 && < 0.9,
144+ test-framework-hunit >= 0.2.0 && < 0.4
145+ Extensions: CPP,
146+ ScopedTypeVariables,
147+ DeriveDataTypeable,
148+ GeneralizedNewtypeDeriving
149+ ghc-options: -Wall -debug -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
150+ HS-Source-Dirs: tests
151+
152+
153+Executable distributed-process-throughput
154+ if flag(benchmarks)
155+ Build-Depends: base >= 4.4 && < 5,
156+ distributed-process,
157+ network-transport-tcp >= 0.3 && < 0.4,
158+ bytestring >= 0.9 && < 0.11,
159+ binary >= 0.5 && < 0.7
160+ else
161+ buildable: False
162+ Main-Is: benchmarks/Throughput.hs
163+ ghc-options: -Wall
164+ Extensions: BangPatterns
165+
166+Executable distributed-process-latency
167+ if flag(benchmarks)
168+ Build-Depends: base >= 4.4 && < 5,
169+ distributed-process,
170+ network-transport-tcp >= 0.3 && < 0.4,
171+ bytestring >= 0.9 && < 0.11,
172+ binary >= 0.5 && < 0.7
173+ else
174+ buildable: False
175+ Main-Is: benchmarks/Latency.hs
176+ ghc-options: -Wall
177+ Extensions: BangPatterns
178+
179+Executable distributed-process-channels
180+ if flag(benchmarks)
181+ Build-Depends: base >= 4.4 && < 5,
182+ distributed-process,
183+ network-transport-tcp >= 0.3 && < 0.4,
184+ bytestring >= 0.9 && < 0.11,
185+ binary >= 0.5 && < 0.7
186+ else
187+ buildable: False
188+ Main-Is: benchmarks/Channels.hs
189+ ghc-options: -Wall
190+ Extensions: BangPatterns
191+
192+Executable distributed-process-spawns
193+ if flag(benchmarks)
194+ Build-Depends: base >= 4.4 && < 5,
195+ distributed-process,
196+ network-transport-tcp >= 0.3 && < 0.4,
197+ bytestring >= 0.9 && < 0.11,
198+ binary >= 0.5 && < 0.7
199+ else
200+ buildable: False
201+ Main-Is: benchmarks/Spawns.hs
202+ ghc-options: -Wall
203+ Extensions: BangPatterns
src/Control/Distributed/Process.hs → distributed-process/src/Control/Distributed/Process.hsrenamed+146−138
@@ -1,23 +1,16 @@
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−-}
17module Control.Distributed.Process
10module 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
@@ -25,7 +18,6 @@ module Control.Distributed.Process
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
22 , expectTimeout
31 -- * Channels
23 -- * Channels
@@ -38,13 +30,6 @@ module Control.Distributed.Process
38 , receiveChanTimeout
30 , receiveChanTimeout
39 , mergePortsBiased
31 , mergePortsBiased
40 , mergePortsRR
32 , mergePortsRR
41− -- * Unsafe messaging variants
42− , unsafeSend
43− , unsafeUSend
44− , unsafeSendChan
45− , unsafeNSend
46− , unsafeNSendRemote
47− , unsafeWrapMessage
48 -- * Advanced messaging
33 -- * Advanced messaging
49 , Match
34 , Match
50 , receiveWait
35 , receiveWait
@@ -52,25 +37,10 @@ module Control.Distributed.Process
52 , match
37 , match
53 , matchIf
38 , matchIf
54 , matchUnknown
39 , matchUnknown
40+ , AbstractMessage(..)
55 , matchAny
41 , matchAny
56 , matchAnyIf
42 , matchAnyIf
57 , matchChan
43 , 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
44 -- * Process management
75 , spawn
45 , spawn
76 , call
46 , call
@@ -87,9 +57,6 @@ module Control.Distributed.Process
87 , getSelfNode
57 , getSelfNode
88 , ProcessInfo(..)
58 , ProcessInfo(..)
89 , getProcessInfo
59 , getProcessInfo
90− , NodeStats(..)
91− , getNodeStats
92− , getLocalNodeStats
93 -- * Monitoring and linking
60 -- * Monitoring and linking
94 , link
61 , link
95 , linkNode
62 , linkNode
@@ -102,7 +69,6 @@ module Control.Distributed.Process
102 , monitorPort
69 , monitorPort
103 , unmonitor
70 , unmonitor
104 , withMonitor
71 , withMonitor
105− , withMonitor_
106 , MonitorRef -- opaque
72 , MonitorRef -- opaque
107 , ProcessLinkException(..)
73 , ProcessLinkException(..)
108 , NodeLinkException(..)
74 , NodeLinkException(..)
@@ -139,7 +105,6 @@ module Control.Distributed.Process
139 , catches
105 , catches
140 , try
106 , try
141 , mask
107 , mask
142− , mask_
143 , onException
108 , onException
144 , bracket
109 , bracket
145 , bracket_
110 , bracket_
@@ -154,27 +119,27 @@ module Control.Distributed.Process
154 -- * Local versions of 'spawn'
119 -- * Local versions of 'spawn'
155 , spawnLocal
120 , spawnLocal
156 , spawnChannelLocal
121 , spawnChannelLocal
157− , callLocal
158 -- * Reconnecting
122 -- * Reconnecting
159 , reconnect
123 , reconnect
160 , reconnectPort
124 , reconnectPort
161 ) where
125 ) where
162
126
127+#if ! MIN_VERSION_base(4,6,0)
128+import Prelude hiding (catch)
129+#endif
130+
131+import Data.Typeable (Typeable)
163import Control.Monad.IO.Class (liftIO)
132import Control.Monad.IO.Class (liftIO)
164−import Control.Applicative
133+import Control.Applicative ((<$>))
165import Control.Monad.Reader (ask)
134import Control.Monad.Reader (ask)
166−import Control.Concurrent (killThread)
135+import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
167−import Control.Concurrent.MVar
168− ( MVar
169− , newEmptyMVar
170− , takeMVar
171− , putMVar
172− )
173import Control.Distributed.Static
136import Control.Distributed.Static
174 ( Closure
137 ( Closure
175 , closure
138 , closure
176 , Static
139 , Static
177 , RemoteTable
140 , RemoteTable
141+ , closureCompose
142+ , staticClosure
178 )
143 )
179import Control.Distributed.Process.Internal.Types
144import Control.Distributed.Process.Internal.Types
180 ( NodeId(..)
145 ( NodeId(..)
@@ -197,14 +162,24 @@ import Control.Distributed.Process.Internal.Types
197 , WhereIsReply(..)
162 , WhereIsReply(..)
198 , RegisterReply(..)
163 , RegisterReply(..)
199 , LocalProcess(processNode)
164 , LocalProcess(processNode)
200− , Message
165+ , nullProcessId
201− , localProcessWithId
166+ )
167+import Control.Distributed.Process.Serializable (Serializable, SerializableDict)
168+import Control.Distributed.Process.Internal.Closure.BuiltIn
169+ ( sdictSendPort
170+ , sndStatic
171+ , idCP
172+ , seqCP
173+ , bindCP
174+ , splitCP
175+ , cpLink
176+ , cpSend
177+ , cpNewChan
178+ , cpDelay
202 )
179 )
203−import Control.Distributed.Process.Serializable (Serializable)
204import Control.Distributed.Process.Internal.Primitives
180import Control.Distributed.Process.Internal.Primitives
205 ( -- Basic messaging
181 ( -- Basic messaging
206 send
182 send
207− , usend
208 , expect
183 , expect
209 -- Channels
184 -- Channels
210 , newChan
185 , newChan
@@ -212,11 +187,6 @@ import Control.Distributed.Process.Internal.Primitives
212 , receiveChan
187 , receiveChan
213 , mergePortsBiased
188 , mergePortsBiased
214 , mergePortsRR
189 , mergePortsRR
215− , unsafeSend
216− , unsafeUSend
217− , unsafeSendChan
218− , unsafeNSend
219− , unsafeNSendRemote
220 -- Advanced messaging
190 -- Advanced messaging
221 , Match
191 , Match
222 , receiveWait
192 , receiveWait
@@ -224,25 +194,10 @@ import Control.Distributed.Process.Internal.Primitives
224 , match
194 , match
225 , matchIf
195 , matchIf
226 , matchUnknown
196 , matchUnknown
197+ , AbstractMessage(..)
227 , matchAny
198 , matchAny
228 , matchAnyIf
199 , matchAnyIf
229 , matchChan
200 , 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
201 -- Process management
247 , terminate
202 , terminate
248 , ProcessTerminationException(..)
203 , ProcessTerminationException(..)
@@ -255,9 +210,6 @@ import Control.Distributed.Process.Internal.Primitives
255 , getSelfNode
210 , getSelfNode
256 , ProcessInfo(..)
211 , ProcessInfo(..)
257 , getProcessInfo
212 , getProcessInfo
258− , NodeStats(..)
259− , getNodeStats
260− , getLocalNodeStats
261 -- Monitoring and linking
213 -- Monitoring and linking
262 , link
214 , link
263 , linkNode
215 , linkNode
@@ -270,7 +222,6 @@ import Control.Distributed.Process.Internal.Primitives
270 , monitorPort
222 , monitorPort
271 , unmonitor
223 , unmonitor
272 , withMonitor
224 , withMonitor
273− , withMonitor_
274 -- Logging
225 -- Logging
275 , say
226 , say
276 -- Registry
227 -- Registry
@@ -293,7 +244,6 @@ import Control.Distributed.Process.Internal.Primitives
293 , catches
244 , catches
294 , try
245 , try
295 , mask
246 , mask
296− , mask_
297 , onException
247 , onException
298 , bracket
248 , bracket
299 , bracket_
249 , bracket_
@@ -307,30 +257,6 @@ import Control.Distributed.Process.Internal.Primitives
307 , reconnectPort
257 , reconnectPort
308 )
258 )
309import Control.Distributed.Process.Node (forkProcess)
259import 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
260
335-- INTERNAL NOTES
261-- INTERNAL NOTES
336--
262--
@@ -342,11 +268,7 @@ import Data.Foldable (forM_)
342-- 2. 'send' may block (when the system TCP buffers are full, while we are
268-- 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
269-- 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
270-- 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
271+-- 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--
272--
351-- 3. Message delivery is reliable and ordered. That means that if process A
273-- 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
274-- sends messages m1, m2, m3 to process B, B will either arrive all three
@@ -395,6 +317,116 @@ import Data.Foldable (forM_)
395-- [6] "Delivery of Messages", post on erlang-questions
317-- [6] "Delivery of Messages", post on erlang-questions
396-- http://erlang.org/pipermail/erlang-questions/2012-February/064767.html
318-- http://erlang.org/pipermail/erlang-questions/2012-February/064767.html
397
319
320+--------------------------------------------------------------------------------
321+-- Primitives are defined in a separate module; here we only define derived --
322+-- constructs --
323+--------------------------------------------------------------------------------
324+
325+-- | Spawn a process
326+--
327+-- For more information about 'Closure', see
328+-- "Control.Distributed.Process.Closure".
329+--
330+-- See also 'call'.
331+spawn :: NodeId -> Closure (Process ()) -> Process ProcessId
332+spawn nid proc = do
333+ us <- getSelfPid
334+ mRef <- monitorNode nid
335+ sRef <- spawnAsync nid (cpDelay us proc)
336+ receiveWait [
337+ matchIf (\(DidSpawn ref _) -> ref == sRef) $ \(DidSpawn _ pid) -> do
338+ unmonitor mRef
339+ send pid ()
340+ return pid
341+ , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef) $ \_ ->
342+ return (nullProcessId nid)
343+ ]
344+
345+-- | Spawn a process and link to it
346+--
347+-- Note that this is just the sequential composition of 'spawn' and 'link'.
348+-- (The "Unified" semantics that underlies Cloud Haskell does not even support
349+-- a synchronous link operation)
350+spawnLink :: NodeId -> Closure (Process ()) -> Process ProcessId
351+spawnLink nid proc = do
352+ pid <- spawn nid proc
353+ link pid
354+ return pid
355+
356+-- | Like 'spawnLink', but monitor the spawned process
357+spawnMonitor :: NodeId -> Closure (Process ()) -> Process (ProcessId, MonitorRef)
358+spawnMonitor nid proc = do
359+ pid <- spawn nid proc
360+ ref <- monitor pid
361+ return (pid, ref)
362+
363+-- | Run a process remotely and wait for it to reply
364+--
365+-- We monitor the remote process: if it dies before it can send a reply, we die
366+-- too.
367+--
368+-- For more information about 'Static', 'SerializableDict', and 'Closure', see
369+-- "Control.Distributed.Process.Closure".
370+--
371+-- See also 'spawn'.
372+call :: Serializable a
373+ => Static (SerializableDict a)
374+ -> NodeId
375+ -> Closure (Process a)
376+ -> Process a
377+call dict nid proc = do
378+ us <- getSelfPid
379+ (pid, mRef) <- spawnMonitor nid (proc `bindCP` cpSend dict us)
380+ -- We are guaranteed to receive the reply before the monitor notification
381+ -- (if a reply is sent at all)
382+ -- NOTE: This might not be true if we switch to unreliable delivery.
383+ mResult <- receiveWait
384+ [ match (return . Right)
385+ , matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)
386+ (\(ProcessMonitorNotification _ _ reason) -> return (Left reason))
387+ ]
388+ case mResult of
389+ Right a -> do
390+ -- Wait for the monitor message so that we the mailbox doesn't grow
391+ receiveWait
392+ [ matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)
393+ (\(ProcessMonitorNotification {}) -> return ())
394+ ]
395+ -- Clean up connection to pid
396+ reconnect pid
397+ return a
398+ Left err ->
399+ fail $ "call: remote process died: " ++ show err
400+
401+-- | Spawn a child process, have the child link to the parent and the parent
402+-- monitor the child
403+spawnSupervised :: NodeId
404+ -> Closure (Process ())
405+ -> Process (ProcessId, MonitorRef)
406+spawnSupervised nid proc = do
407+ us <- getSelfPid
408+ them <- spawn nid (cpLink us `seqCP` proc)
409+ ref <- monitor them
410+ return (them, ref)
411+
412+-- | Spawn a new process, supplying it with a new 'ReceivePort' and return
413+-- the corresponding 'SendPort'.
414+spawnChannel :: forall a. Typeable a => Static (SerializableDict a)
415+ -> NodeId
416+ -> Closure (ReceivePort a -> Process ())
417+ -> Process (SendPort a)
418+spawnChannel dict nid proc = do
419+ us <- getSelfPid
420+ _ <- spawn nid (go us)
421+ expect
422+ where
423+ go :: ProcessId -> Closure (Process ())
424+ go pid = cpNewChan dict
425+ `bindCP`
426+ (cpSend (sdictSendPort dict) pid `splitCP` proc)
427+ `bindCP`
428+ (idCP `closureCompose` staticClosure sndStatic)
429+
398--------------------------------------------------------------------------------
430--------------------------------------------------------------------------------
399-- Local versions of spawn --
431-- Local versions of spawn --
400--------------------------------------------------------------------------------
432--------------------------------------------------------------------------------
@@ -421,27 +453,3 @@ spawnChannelLocal proc = do
421 liftIO $ putMVar mvar sport
453 liftIO $ putMVar mvar sport
422 proc rport
454 proc rport
423 takeMVar mvar
455 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−22
@@ -163,8 +163,6 @@ module Control.Distributed.Process.Closure
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
@@ -175,17 +173,9 @@ module Control.Distributed.Process.Closure
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
@@ -205,8 +195,6 @@ import Control.Distributed.Process.Internal.Closure.BuiltIn
205 , sdictUnit
195 , sdictUnit
206 , sdictProcessId
196 , sdictProcessId
207 , sdictSendPort
197 , sdictSendPort
208− , sdictStatic
209− , sdictClosure
210 -- The CP type and associated combinators
198 -- The CP type and associated combinators
211 , CP
199 , CP
212 , idCP
200 , idCP
@@ -217,20 +205,10 @@ import Control.Distributed.Process.Internal.Closure.BuiltIn
217 -- CP versions of Cloud Haskell primitives
205 -- CP versions of Cloud Haskell primitives
218 , cpLink
206 , cpLink
219 , cpUnlink
207 , cpUnlink
220− , cpRelay
221 , cpSend
208 , cpSend
222 , cpExpect
209 , cpExpect
223 , cpNewChan
210 , cpNewChan
224 )
211 )
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
212#ifdef TemplateHaskellSupport
235import Control.Distributed.Process.Internal.Closure.TH
213import Control.Distributed.Process.Internal.Closure.TH
236 ( remotable
214 ( remotable
src/Control/Distributed/Process/Internal/CQueue.hs → distributed-process/src/Control/Distributed/Process/Internal/CQueue.hsrenamed+15−72
@@ -1,18 +1,13 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DeriveFunctor #-}
3−{-# LANGUAGE BangPatterns #-}
4−{-# LANGUAGE MagicHash, UnboxedTuples, PatternGuards, ScopedTypeVariables, RankNTypes #-}
5-- | Concurrent queue for single reader, single writer
1-- | Concurrent queue for single reader, single writer
2+{-# LANGUAGE MagicHash, UnboxedTuples, PatternGuards #-}
6module Control.Distributed.Process.Internal.CQueue
3module Control.Distributed.Process.Internal.CQueue
7 ( CQueue
4 ( CQueue
8 , BlockSpec(..)
5 , BlockSpec(..)
9 , MatchOn(..)
6 , MatchOn(..)
10 , newCQueue
7 , newCQueue
11 , enqueue
8 , enqueue
12− , enqueueSTM
13 , dequeue
9 , dequeue
14 , mkWeakCQueue
10 , mkWeakCQueue
15− , queueSize
16 ) where
11 ) where
17
12
18import Prelude hiding (length, reverse)
13import Prelude hiding (length, reverse)
@@ -20,14 +15,10 @@ import Control.Concurrent.STM
20 ( atomically
15 ( atomically
21 , STM
16 , STM
22 , TChan
17 , TChan
23− , TVar
24− , modifyTVar'
25 , tryReadTChan
18 , tryReadTChan
26 , newTChan
19 , newTChan
27− , newTVarIO
28 , writeTChan
20 , writeTChan
29 , readTChan
21 , readTChan
30− , readTVarIO
31 , orElse
22 , orElse
32 , retry
23 , retry
33 )
24 )
@@ -45,56 +36,35 @@ import Control.Distributed.Process.Internal.StrictList
45 , append
36 , append
46 )
37 )
47import Data.Maybe (fromJust)
38import Data.Maybe (fromJust)
48−import Data.Traversable (traverse)
49import GHC.MVar (MVar(MVar))
39import GHC.MVar (MVar(MVar))
50−import GHC.IO (IO(IO), unIO)
40+import GHC.IO (IO(IO))
51−import GHC.Exts (mkWeak#)
41+import GHC.Prim (mkWeak#)
52import GHC.Weak (Weak(Weak))
42import GHC.Weak (Weak(Weak))
53
43
54-- We use a TCHan rather than a Chan so that we have a non-blocking read
44-- We use a TCHan rather than a Chan so that we have a non-blocking read
55data CQueue a = CQueue (StrictMVar (StrictList a)) -- Arrived
45data CQueue a = CQueue (StrictMVar (StrictList a)) -- Arrived
56 (TChan a) -- Incoming
46 (TChan a) -- Incoming
57− (TVar Int) -- Queue size
58
47
59newCQueue :: IO (CQueue a)
48newCQueue :: IO (CQueue a)
60−newCQueue = CQueue <$> newMVar Nil <*> atomically newTChan <*> newTVarIO 0
49+newCQueue = CQueue <$> newMVar Nil <*> atomically newTChan
61
50
62-- | Enqueue an element
51-- | Enqueue an element
63--
52--
64-- Enqueue is strict.
53-- Enqueue is strict.
65enqueue :: CQueue a -> a -> IO ()
54enqueue :: CQueue a -> a -> IO ()
66−enqueue c !a = atomically (enqueueSTM c a)
67−
68−-- | Variant of enqueue for use in the STM monad.
69−enqueueSTM :: CQueue a -> a -> STM ()
70−enqueueSTM (CQueue _arrived incoming size) !a = do
71− writeTChan incoming a
55+enqueue (CQueue _arrived incoming) !a = atomically $ writeTChan incoming a
72− modifyTVar' size succ
73
56
74data BlockSpec =
57data BlockSpec =
75 NonBlocking
58 NonBlocking
76 | Blocking
59 | Blocking
77 | Timeout Int
60 | Timeout Int
78
61
79−-- Match operations
80−--
81−-- They can be either a message match or a channel match.
82data MatchOn m a
62data MatchOn m a
83 = MatchMsg (m -> Maybe a)
63 = MatchMsg (m -> Maybe a)
84 | MatchChan (STM a)
64 | MatchChan (STM a)
85− deriving (Functor)
86
65
87−-- Lists of chunks of matches
88−--
89−-- Two consecutive chunks never have the same kind of matches. i.e. if one chunk
90−-- contains message matches then the next one must contain channel matches and
91−-- viceversa.
92type MatchChunks m a = [Either [m -> Maybe a] [STM a]]
66type MatchChunks m a = [Either [m -> Maybe a] [STM a]]
93
67
94−-- Splits a list of matches into chunks.
95−--
96−-- > concatMap (either (map MatchMsg) (map MatchChan)) . chunkMatches == id
97−--
98chunkMatches :: [MatchOn m a] -> MatchChunks m a
68chunkMatches :: [MatchOn m a] -> MatchChunks m a
99chunkMatches [] = []
69chunkMatches [] = []
100chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest
70chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest
@@ -102,7 +72,6 @@ chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest
102chunkMatches (MatchChan r : ms) = Right (r : chk) : chunkMatches rest
72chunkMatches (MatchChan r : ms) = Right (r : chk) : chunkMatches rest
103 where (chk, rest) = spanMatchChan ms
73 where (chk, rest) = spanMatchChan ms
104
74
105−-- | @spanMatchMsg = first (map (\(MatchMsg x) -> x)) . span isMatchMsg@
106spanMatchMsg :: [MatchOn m a] -> ([m -> Maybe a], [MatchOn m a])
75spanMatchMsg :: [MatchOn m a] -> ([m -> Maybe a], [MatchOn m a])
107spanMatchMsg [] = ([],[])
76spanMatchMsg [] = ([],[])
108spanMatchMsg (m : ms)
77spanMatchMsg (m : ms)
@@ -110,7 +79,6 @@ spanMatchMsg (m : ms)
110 | otherwise = ([], m:ms)
79 | otherwise = ([], m:ms)
111 where !(msgs,rest) = spanMatchMsg ms
80 where !(msgs,rest) = spanMatchMsg ms
112
81
113−-- | @spanMatchMsg = first (map (\(MatchChan x) -> x)) . span isMatchChan@
114spanMatchChan :: [MatchOn m a] -> ([STM a], [MatchOn m a])
82spanMatchChan :: [MatchOn m a] -> ([STM a], [MatchOn m a])
115spanMatchChan [] = ([],[])
83spanMatchChan [] = ([],[])
116spanMatchChan (m : ms)
84spanMatchChan (m : ms)
@@ -127,7 +95,7 @@ dequeue :: forall m a.
127 -> BlockSpec -- ^ Blocking behaviour
95 -> BlockSpec -- ^ Blocking behaviour
128 -> [MatchOn m a] -- ^ List of matches
96 -> [MatchOn m a] -- ^ List of matches
129 -> IO (Maybe a) -- ^ 'Nothing' only on timeout
97 -> IO (Maybe a) -- ^ 'Nothing' only on timeout
130−dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJust $
98+dequeue (CQueue arrived incoming) blockSpec matchons =
131 case blockSpec of
99 case blockSpec of
132 Timeout n -> timeout n $ fmap fromJust run
100 Timeout n -> timeout n $ fmap fromJust run
133 _other ->
101 _other ->
@@ -139,17 +107,9 @@ dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJus
139 -- no onException needed
107 -- no onException needed
140 _other -> run
108 _other -> run
141 where
109 where
142− -- Decrement counter is smth is returned from the queue,
143− -- this is safe to use as method is called under a mask
144− -- and there is no 'unmasked' operation inside
145− decrementJust :: IO (Maybe (Either a a)) -> IO (Maybe a)
146− decrementJust f =
147− traverse (either return (\x -> decrement >> return x)) =<< f
148− decrement = atomically $ modifyTVar' size pred
149−
150 chunks = chunkMatches matchons
110 chunks = chunkMatches matchons
151
111
152− run = do
112+ run = mask_ $ do
153 arr <- takeMVar arrived
113 arr <- takeMVar arrived
154 let grabNew xs = do
114 let grabNew xs = do
155 r <- atomically $ tryReadTChan incoming
115 r <- atomically $ tryReadTChan incoming
@@ -159,25 +119,17 @@ dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJus
159 arr' <- grabNew arr
119 arr' <- grabNew arr
160 goCheck chunks arr'
120 goCheck chunks arr'
161
121
162− -- Yields the value of the first succesful STM transaction as
163− -- @Just (Left v)@. If all transactions fail, yields the value of the second
164− -- argument.
165− waitChans :: [STM a] -> STM (Maybe (Either a a)) -> STM (Maybe (Either a a))
166 waitChans ports on_block =
122 waitChans ports on_block =
167− foldr orElse on_block (map (fmap (Just . Left)) ports)
123+ foldr orElse on_block (map (fmap Just) ports)
168
124
169 --
125 --
170 -- First check the MatchChunks against the messages already in the
126 -- First check the MatchChunks against the messages already in the
171 -- mailbox. For channel matches, we do a non-blocking check at
127 -- mailbox. For channel matches, we do a non-blocking check at
172 -- this point.
128 -- this point.
173 --
129 --
174− -- Yields @Just (Left a)@ when a channel is matched, @Just (Right a)@
175− -- when a message is matched and @Nothing@ when there are no messages and we
176− -- aren't blocking.
177− --
178 goCheck :: MatchChunks m a
130 goCheck :: MatchChunks m a
179 -> StrictList m -- messages to check, in this order
131 -> StrictList m -- messages to check, in this order
180− -> IO (Maybe (Either a a))
132+ -> IO (Maybe a)
181
133
182 goCheck [] old = goWait old
134 goCheck [] old = goWait old
183
135
@@ -194,7 +146,7 @@ dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJus
194 -- of passing around restore and setting up exception handlers is
146 -- of passing around restore and setting up exception handlers is
195 -- high. So just don't use expensive matchIfs!
147 -- high. So just don't use expensive matchIfs!
196 case checkArrived matches old of
148 case checkArrived matches old of
197− (old', Just r) -> returnOld old' (Just (Right r))
149+ (old', Just r) -> returnOld old' (Just r)
198 (old', Nothing) -> goCheck rest old'
150 (old', Nothing) -> goCheck rest old'
199 -- use the result list, which is now left-biased
151 -- use the result list, which is now left-biased
200
152
@@ -223,7 +175,6 @@ dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJus
223 -- Contents of 'arrived' from now on is (old ++ new), and
175 -- Contents of 'arrived' from now on is (old ++ new), and
224 -- messages that arrive are snocced onto new.
176 -- messages that arrive are snocced onto new.
225 --
177 --
226− goWait :: StrictList m -> IO (Maybe (Either a a))
227 goWait old = do
178 goWait old = do
228 r <- waitIncoming `onException` putMVar arrived old
179 r <- waitIncoming `onException` putMVar arrived old
229 case r of
180 case r of
@@ -241,7 +192,7 @@ dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJus
241 --
192 --
242 -- Right => message arrived on a channel first
193 -- Right => message arrived on a channel first
243 --
194 --
244− Right a -> returnOld old (Just (Left a))
195+ Right a -> returnOld old (Just a)
245
196
246 --
197 --
247 -- A message arrived in the process inbox; check the MatchChunks for
198 -- A message arrived in the process inbox; check the MatchChunks for
@@ -250,7 +201,7 @@ dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJus
250 goCheck1 :: MatchChunks m a
201 goCheck1 :: MatchChunks m a
251 -> m -- single message to check
202 -> m -- single message to check
252 -> StrictList m -- old messages we have already checked
203 -> StrictList m -- old messages we have already checked
253− -> IO (Maybe (Either a a))
204+ -> IO (Maybe a)
254
205
255 goCheck1 [] m old = goWait (Snoc old m)
206 goCheck1 [] m old = goWait (Snoc old m)
256
207
@@ -263,17 +214,16 @@ dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJus
263 goCheck1 (Left matches : rest) m old = do
214 goCheck1 (Left matches : rest) m old = do
264 case checkMatches matches m of
215 case checkMatches matches m of
265 Nothing -> goCheck1 rest m old
216 Nothing -> goCheck1 rest m old
266− Just p -> returnOld old (Just (Right p))
217+ Just p -> returnOld old (Just p)
267
218
268 -- a common pattern for putting back the arrived queue at the end
219 -- a common pattern for putting back the arrived queue at the end
269− returnOld :: StrictList m -> Maybe (Either a a) -> IO (Maybe (Either a a))
220+ returnOld :: StrictList m -> Maybe a -> IO (Maybe a)
270 returnOld old r = do putMVar arrived old; return r
221 returnOld old r = do putMVar arrived old; return r
271
222
272 -- as a side-effect, this left-biases the list
223 -- as a side-effect, this left-biases the list
273 checkArrived :: [m -> Maybe a] -> StrictList m -> (StrictList m, Maybe a)
224 checkArrived :: [m -> Maybe a] -> StrictList m -> (StrictList m, Maybe a)
274 checkArrived matches list = go list Nil
225 checkArrived matches list = go list Nil
275 where
226 where
276− -- @go xs ys@ searches for a message match in @append xs ys@
277 go Nil Nil = (Nil, Nothing)
227 go Nil Nil = (Nil, Nothing)
278 go Nil r = go r Nil
228 go Nil r = go r Nil
279 go (Append xs ys) tl = go xs (append ys tl)
229 go (Append xs ys) tl = go xs (append ys tl)
@@ -289,12 +239,5 @@ dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJus
289
239
290-- | Weak reference to a CQueue
240-- | Weak reference to a CQueue
291mkWeakCQueue :: CQueue a -> IO () -> IO (Weak (CQueue a))
241mkWeakCQueue :: CQueue a -> IO () -> IO (Weak (CQueue a))
292−mkWeakCQueue m@(CQueue (StrictMVar (MVar m#)) _ _) f = IO $ \s ->
242+mkWeakCQueue m@(CQueue (StrictMVar (MVar m#)) _) f = IO $ \s ->
293−#if MIN_VERSION_base(4,9,0)
294− case mkWeak# m# m (unIO f) s of (# s1, w #) -> (# s1, Weak w #)
295−#else
296 case mkWeak# m# m f s of (# s1, w #) -> (# s1, Weak w #)
243 case mkWeak# m# m f s of (# s1, w #) -> (# s1, Weak w #)
297−#endif
298−
299−queueSize :: CQueue a -> IO Int
300−queueSize (CQueue _ _ size) = readTVarIO size
src/Control/Distributed/Process/Internal/Closure/BuiltIn.hs → distributed-process/src/Control/Distributed/Process/Internal/Closure/BuiltIn.hsrenamed+5−48
@@ -1,5 +1,3 @@
1−{-# LANGUAGE ScopedTypeVariables #-}
2−{-# LANGUAGE RankNTypes #-}
3module Control.Distributed.Process.Internal.Closure.BuiltIn
1module Control.Distributed.Process.Internal.Closure.BuiltIn
4 ( -- * Remote table
2 ( -- * Remote table
5 remoteTable
3 remoteTable
@@ -8,8 +6,6 @@ module Control.Distributed.Process.Internal.Closure.BuiltIn
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
@@ -20,16 +16,13 @@ module Control.Distributed.Process.Internal.Closure.BuiltIn
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
24 -- * Support for some CH operations
31− , cpDelayed
25+ , cpDelay
32− , cpEnableTraceRemote
33 ) where
26 ) where
34
27
35import Data.ByteString.Lazy (ByteString)
28import Data.ByteString.Lazy (ByteString)
@@ -52,7 +45,6 @@ import Control.Distributed.Static
52import Control.Distributed.Process.Serializable
45import Control.Distributed.Process.Serializable
53 ( SerializableDict(..)
46 ( SerializableDict(..)
54 , Serializable
47 , Serializable
55− , TypeableDict(..)
56 )
48 )
57import Control.Distributed.Process.Internal.Types
49import Control.Distributed.Process.Internal.Types
58 ( Process
50 ( Process
@@ -64,7 +56,6 @@ import Control.Distributed.Process.Internal.Types
64import Control.Distributed.Process.Internal.Primitives
56import Control.Distributed.Process.Internal.Primitives
65 ( link
57 ( link
66 , unlink
58 , unlink
67− , relay
68 , send
59 , send
69 , expect
60 , expect
70 , newChan
61 , newChan
@@ -85,15 +76,12 @@ remoteTable =
85 . registerStatic "$sdictUnit" (toDynamic (SerializableDict :: SerializableDict ()))
76 . registerStatic "$sdictUnit" (toDynamic (SerializableDict :: SerializableDict ()))
86 . registerStatic "$sdictProcessId" (toDynamic (SerializableDict :: SerializableDict ProcessId))
77 . registerStatic "$sdictProcessId" (toDynamic (SerializableDict :: SerializableDict ProcessId))
87 . registerStatic "$sdictSendPort_" (toDynamic (sdictSendPort_ :: SerializableDict ANY -> SerializableDict (SendPort ANY)))
78 . 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))
79 . registerStatic "$returnProcess" (toDynamic (return :: ANY -> Process ANY))
91 . registerStatic "$seqProcess" (toDynamic ((>>) :: Process ANY1 -> Process ANY2 -> Process ANY2))
80 . registerStatic "$seqProcess" (toDynamic ((>>) :: Process ANY1 -> Process ANY2 -> Process ANY2))
92 . registerStatic "$bindProcess" (toDynamic ((>>=) :: Process ANY1 -> (ANY1 -> Process ANY2) -> Process ANY2))
81 . registerStatic "$bindProcess" (toDynamic ((>>=) :: Process ANY1 -> (ANY1 -> Process ANY2) -> Process ANY2))
93 . registerStatic "$decodeProcessId" (toDynamic (decode :: ByteString -> ProcessId))
82 . registerStatic "$decodeProcessId" (toDynamic (decode :: ByteString -> ProcessId))
94 . registerStatic "$link" (toDynamic link)
83 . registerStatic "$link" (toDynamic link)
95 . registerStatic "$unlink" (toDynamic unlink)
84 . registerStatic "$unlink" (toDynamic unlink)
96− . registerStatic "$relay" (toDynamic relay)
97 . registerStatic "$sendDict" (toDynamic (sendDict :: SerializableDict ANY -> ProcessId -> ANY -> Process ()))
85 . registerStatic "$sendDict" (toDynamic (sendDict :: SerializableDict ANY -> ProcessId -> ANY -> Process ()))
98 . registerStatic "$expectDict" (toDynamic (expectDict :: SerializableDict ANY -> Process ANY))
86 . registerStatic "$expectDict" (toDynamic (expectDict :: SerializableDict ANY -> Process ANY))
99 . registerStatic "$newChanDict" (toDynamic (newChanDict :: SerializableDict ANY -> Process (SendPort ANY, ReceivePort ANY)))
87 . registerStatic "$newChanDict" (toDynamic (newChanDict :: SerializableDict ANY -> Process (SendPort ANY, ReceivePort ANY)))
@@ -107,12 +95,6 @@ remoteTable =
107 sdictSendPort_ :: forall a. SerializableDict a -> SerializableDict (SendPort a)
95 sdictSendPort_ :: forall a. SerializableDict a -> SerializableDict (SendPort a)
108 sdictSendPort_ SerializableDict = SerializableDict
96 sdictSendPort_ SerializableDict = SerializableDict
109
97
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 ()
98 sendDict :: forall a. SerializableDict a -> ProcessId -> a -> Process ()
117 sendDict SerializableDict = send
99 sendDict SerializableDict = send
118
100
@@ -155,14 +137,6 @@ sdictSendPort :: Typeable a
155 => Static (SerializableDict a) -> Static (SerializableDict (SendPort a))
137 => Static (SerializableDict a) -> Static (SerializableDict (SendPort a))
156sdictSendPort = staticApply (staticLabel "$sdictSendPort_")
138sdictSendPort = staticApply (staticLabel "$sdictSendPort_")
157
139
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−
166--------------------------------------------------------------------------------
140--------------------------------------------------------------------------------
167-- Static values --
141-- Static values --
168--------------------------------------------------------------------------------
142--------------------------------------------------------------------------------
@@ -272,22 +246,6 @@ cpNewChan dict = staticClosure (newChanDictStatic `staticApply` dict)
272 => Static (SerializableDict a -> Process (SendPort a, ReceivePort a))
246 => Static (SerializableDict a -> Process (SendPort a, ReceivePort a))
273 newChanDictStatic = staticLabel "$newChanDict"
247 newChanDictStatic = staticLabel "$newChanDict"
274
248
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--------------------------------------------------------------------------------
249--------------------------------------------------------------------------------
292-- Support for spawn --
250-- Support for spawn --
293--------------------------------------------------------------------------------
251--------------------------------------------------------------------------------
@@ -295,20 +253,19 @@ cpEnableTraceRemote =
295-- | @delay them p@ is a process that waits for a signal (a message of type @()@)
253-- | @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
254-- 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
255-- avoid waiting forever, @delay them p@ monitors 'them'. If it receives a
298−-- monitor message instead, it proceeds as @p@ too.
256+-- monitor message instead it simply terminates.
299delay :: ProcessId -> Process () -> Process ()
257delay :: ProcessId -> Process () -> Process ()
300delay them p = do
258delay them p = do
301 ref <- monitor them
259 ref <- monitor them
302 let sameRef (ProcessMonitorNotification ref' _ _) = ref == ref'
260 let sameRef (ProcessMonitorNotification ref' _ _) = ref == ref'
303 receiveWait [
261 receiveWait [
304− match $ \() -> unmonitor ref
262+ match $ \() -> unmonitor ref >> p
305 , matchIf sameRef $ \_ -> return ()
263 , matchIf sameRef $ \_ -> return ()
306 ]
264 ]
307− p
308
265
309-- | 'CP' version of 'delay'
266-- | 'CP' version of 'delay'
310−cpDelayed :: ProcessId -> Closure (Process ()) -> Closure (Process ())
267+cpDelay :: ProcessId -> Closure (Process ()) -> Closure (Process ())
311−cpDelayed = closureApply . cpDelay'
268+cpDelay = closureApply . cpDelay'
312 where
269 where
313 cpDelay' :: ProcessId -> Closure (Process () -> Process ())
270 cpDelay' :: ProcessId -> Closure (Process () -> Process ())
314 cpDelay' pid = closure decoder (encode pid)
271 cpDelay' pid = closure decoder (encode pid)
src/Control/Distributed/Process/Internal/Closure/TH.hs → distributed-process/src/Control/Distributed/Process/Internal/Closure/TH.hsrenamed+8−24
@@ -1,5 +1,5 @@
1-- | Template Haskell support
1-- | Template Haskell support
2−{-# LANGUAGE TemplateHaskell, CPP #-}
2+{-# LANGUAGE TemplateHaskell #-}
3module Control.Distributed.Process.Internal.Closure.TH
3module Control.Distributed.Process.Internal.Closure.TH
4 ( -- * User-level API
4 ( -- * User-level API
5 remotable
5 remotable
@@ -29,14 +29,7 @@ import Language.Haskell.TH
29 , Type(AppT, ForallT, VarT, ArrowT)
29 , Type(AppT, ForallT, VarT, ArrowT)
30 , Info(VarI)
30 , Info(VarI)
31 , TyVarBndr(PlainTV, KindedTV)
31 , TyVarBndr(PlainTV, KindedTV)
32− , Pred
32+ , 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
33 -- Lifted constructors
41 -- .. Literals
34 -- .. Literals
42 , stringL
35 , stringL
@@ -251,21 +244,16 @@ generateStatic :: Name -> [TyVarBndr] -> Type -> Q [Dec]
251generateStatic n xs typ = do
244generateStatic n xs typ = do
252 staticTyp <- [t| Static |]
245 staticTyp <- [t| Static |]
253 sequence
246 sequence
254− [ sigD (staticName n) $ do
247+ [ sigD (staticName n) $
255− txs <- sequence $ map typeable xs
256 return (ForallT xs
248 return (ForallT xs
257− txs
249+ (map typeable xs)
258− (staticTyp `AppT` typ))
250+ (staticTyp `AppT` typ)
251+ )
259 , sfnD (staticName n) [| staticLabel $(showFQN n) |]
252 , sfnD (staticName n) [| staticLabel $(showFQN n) |]
260 ]
253 ]
261 where
254 where
262− typeable :: TyVarBndr -> Q Pred
255+ 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)]
256+ typeable tv = ClassP (mkName "Typeable") [VarT (tyVarBndrName tv)]
268−#endif
269
257
270-- | Generate a serialization dictionary with name 'n' for type 'typ'
258-- | Generate a serialization dictionary with name 'n' for type 'typ'
271generateDict :: Name -> Type -> Q [Dec]
259generateDict :: Name -> Type -> Q [Dec]
@@ -303,11 +291,7 @@ getType :: Name -> Q (Name, Type)
303getType name = do
291getType name = do
304 info <- reify name
292 info <- reify name
305 case info of
293 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)
294 VarI origName typ _ _ -> return (origName, typ)
310−#endif
311 _ -> fail $ show name ++ " not found"
295 _ -> fail $ show name ++ " not found"
312
296
313-- | Variation on 'funD' which takes a single expression to define the function
297-- | Variation on 'funD' which takes a single expression to define the function
src/Control/Distributed/Process/Internal/Messaging.hs → distributed-process/src/Control/Distributed/Process/Internal/Messaging.hsrenamed+25−63
@@ -5,7 +5,6 @@ module Control.Distributed.Process.Internal.Messaging
5 , disconnect
5 , disconnect
6 , closeImplicitReconnections
6 , closeImplicitReconnections
7 , impliesDeathOf
7 , impliesDeathOf
8− , sendCtrlMsg
9 ) where
8 ) where
10
9
11import Data.Accessor ((^.), (^=))
10import Data.Accessor ((^.), (^=))
@@ -13,14 +12,9 @@ import Data.Binary (Binary, encode)
13import qualified Data.Map as Map (partitionWithKey, elems)
12import qualified Data.Map as Map (partitionWithKey, elems)
14import qualified Data.ByteString.Lazy as BSL (toChunks)
13import qualified Data.ByteString.Lazy as BSL (toChunks)
15import qualified Data.ByteString as BSS (ByteString)
14import qualified Data.ByteString as BSS (ByteString)
16−import Control.Distributed.Process.Serializable ()
15+import Control.Distributed.Process.Internal.StrictMVar (withMVar, modifyMVar_)
17−
18−import Control.Concurrent (forkIO)
19import Control.Concurrent.Chan (writeChan)
16import Control.Concurrent.Chan (writeChan)
20−import Control.Exception (mask_)
21import Control.Monad (unless)
17import Control.Monad (unless)
22−import Control.Monad.IO.Class (liftIO)
23−import Control.Monad.Reader (ask)
24import qualified Network.Transport as NT
18import qualified Network.Transport as NT
25 ( Connection
19 ( Connection
26 , send
20 , send
@@ -30,10 +24,7 @@ import qualified Network.Transport as NT
30 , close
24 , close
31 )
25 )
32import Control.Distributed.Process.Internal.Types
26import Control.Distributed.Process.Internal.Types
33− ( LocalNode(localEndPoint, localCtrlChan)
27+ ( LocalNode(localState, localEndPoint, localCtrlChan)
34− , withValidLocalState
35− , modifyValidLocalState
36− , modifyValidLocalState_
37 , Identifier
28 , Identifier
38 , localConnections
29 , localConnections
39 , localConnectionBetween
30 , localConnectionBetween
@@ -45,16 +36,12 @@ import Control.Distributed.Process.Internal.Types
45 , ProcessSignal(Died)
36 , ProcessSignal(Died)
46 , DiedReason(DiedDisconnect)
37 , DiedReason(DiedDisconnect)
47 , ImplicitReconnect(WithImplicitReconnect)
38 , ImplicitReconnect(WithImplicitReconnect)
48− , NodeId(..)
39+ , NodeId
49− , ProcessId(..)
40+ , ProcessId(processNodeId)
50− , LocalNode(..)
51− , LocalProcess(..)
52− , Process(..)
53 , SendPortId(sendPortProcessId)
41 , SendPortId(sendPortProcessId)
54 , Identifier(NodeIdentifier, ProcessIdentifier, SendPortIdentifier)
42 , Identifier(NodeIdentifier, ProcessIdentifier, SendPortIdentifier)
55 )
43 )
56import Control.Distributed.Process.Serializable (Serializable)
44import Control.Distributed.Process.Serializable (Serializable)
57−import Data.Foldable (forM_)
58
45
59--------------------------------------------------------------------------------
46--------------------------------------------------------------------------------
60-- Message sending --
47-- Message sending --
@@ -78,7 +65,7 @@ sendPayload node from to implicitReconnect payload = do
78 unless didSend $ do
65 unless didSend $ do
79 writeChan (localCtrlChan node) NCMsg
66 writeChan (localCtrlChan node) NCMsg
80 { ctrlMsgSender = to
67 { ctrlMsgSender = to
81− , ctrlMsgSignal = Died (NodeIdentifier $ nodeOf to) DiedDisconnect
68+ , ctrlMsgSignal = Died to DiedDisconnect
82 }
69 }
83
70
84sendBinary :: Binary a
71sendBinary :: Binary a
@@ -107,7 +94,7 @@ setupConnBetween :: LocalNode
107 -> ImplicitReconnect
94 -> ImplicitReconnect
108 -> IO (Maybe NT.Connection)
95 -> IO (Maybe NT.Connection)
109setupConnBetween node from to implicitReconnect = do
96setupConnBetween node from to implicitReconnect = do
110− mConn <- NT.connect (localEndPoint node)
97+ mConn <- NT.connect endPoint
111 (nodeAddress . nodeOf $ to)
98 (nodeAddress . nodeOf $ to)
112 NT.ReliableOrdered
99 NT.ReliableOrdered
113 NT.defaultConnectHints
100 NT.defaultConnectHints
@@ -118,11 +105,14 @@ setupConnBetween node from to implicitReconnect = do
118 Left _ ->
105 Left _ ->
119 return Nothing
106 return Nothing
120 Right () -> do
107 Right () -> do
121− modifyValidLocalState_ node $
108+ modifyMVar_ nodeState $ return .
122− return . (localConnectionBetween from to ^= Just (conn, implicitReconnect))
109+ (localConnectionBetween from to ^= Just (conn, implicitReconnect))
123 return $ Just conn
110 return $ Just conn
124 Left _ ->
111 Left _ ->
125 return Nothing
112 return Nothing
113+ where
114+ endPoint = localEndPoint node
115+ nodeState = localState node
126
116
127connBetween :: LocalNode
117connBetween :: LocalNode
128 -> Identifier
118 -> Identifier
@@ -130,37 +120,33 @@ connBetween :: LocalNode
130 -> ImplicitReconnect
120 -> ImplicitReconnect
131 -> IO (Maybe NT.Connection)
121 -> IO (Maybe NT.Connection)
132connBetween node from to implicitReconnect = do
122connBetween node from to implicitReconnect = do
133− mConn <- withValidLocalState node $
134− return . (^. localConnectionBetween from to)
123+ mConn <- withMVar nodeState $ return . (^. localConnectionBetween from to)
135 case mConn of
124 case mConn of
136 Just (conn, _) ->
125 Just (conn, _) ->
137 return $ Just conn
126 return $ Just conn
138 Nothing ->
127 Nothing ->
139 setupConnBetween node from to implicitReconnect
128 setupConnBetween node from to implicitReconnect
129+ where
130+ nodeState = localState node
140
131
141disconnect :: LocalNode -> Identifier -> Identifier -> IO ()
132disconnect :: LocalNode -> Identifier -> Identifier -> IO ()
142−disconnect node from to = mask_ $ do
133+disconnect node from to =
143− mio <- modifyValidLocalState node $ \vst ->
134+ modifyMVar_ (localState node) $ \st ->
144− case vst ^. localConnectionBetween from to of
135+ case st ^. localConnectionBetween from to of
145 Nothing ->
136 Nothing ->
146− return (vst, return ())
137+ return st
147 Just (conn, _) -> do
138 Just (conn, _) -> do
139+ NT.close conn
148− return ( localConnectionBetween from to ^= Nothing $ vst
140+ return (localConnectionBetween from to ^= Nothing $ st)
149− , NT.close conn
150− )
151− forM_ mio forkIO
152
141
153closeImplicitReconnections :: LocalNode -> Identifier -> IO ()
142closeImplicitReconnections :: LocalNode -> Identifier -> IO ()
154−closeImplicitReconnections node to = mask_ $ do
143+closeImplicitReconnections node to =
155− mconns <- modifyValidLocalState node $ \vst -> do
144+ modifyMVar_ (localState node) $ \st -> do
156 let shouldClose (_, to') (_, WithImplicitReconnect) = to `impliesDeathOf` to'
145 let shouldClose (_, to') (_, WithImplicitReconnect) = to `impliesDeathOf` to'
157 shouldClose _ _ = False
146 shouldClose _ _ = False
158− let (affected, unaffected) =
159− Map.partitionWithKey shouldClose (vst ^. localConnections)
147+ let (affected, unaffected) = Map.partitionWithKey shouldClose (st ^. localConnections)
160− return ( localConnections ^= unaffected $ vst
161− , map fst $ Map.elems affected
162− )
163− forM_ mconns $ forkIO . mapM_ NT.close
148+ mapM_ (NT.close . fst) (Map.elems affected)
149+ return (localConnections ^= unaffected $ st)
164
150
165-- | @a `impliesDeathOf` b@ is true if the death of @a@ (for instance, a node)
151-- | @a `impliesDeathOf` b@ is true if the death of @a@ (for instance, a node)
166-- implies the death of @b@ (for instance, a process on that node)
152-- implies the death of @b@ (for instance, a process on that node)
@@ -181,27 +167,3 @@ SendPortIdentifier cid `impliesDeathOf` SendPortIdentifier cid' =
181 cid' == cid
167 cid' == cid
182_ `impliesDeathOf` _ =
168_ `impliesDeathOf` _ =
183 False
169 False
184−
185−
186−-- Send a control message. Evaluates the message to HNF before sending it.
187−--
188−-- The message shouldn't produce more errors when further evaluated. If
189−-- evaluation threw errors the node controller or the receiver would crash when
190−-- inspecting it.
191−sendCtrlMsg :: Maybe NodeId -- ^ Nothing for the local node
192− -> ProcessSignal -- ^ Message to send
193− -> Process ()
194−sendCtrlMsg mNid signal = do
195− proc <- ask
196− let msg = NCMsg { ctrlMsgSender = ProcessIdentifier (processId proc)
197− , ctrlMsgSignal = signal
198− }
199− case mNid of
200− Nothing -> do
201− liftIO $ writeChan (localCtrlChan (processNode proc)) $! msg
202− Just nid ->
203− liftIO $ sendBinary (processNode proc)
204− (NodeIdentifier (processNodeId $ processId proc))
205− (NodeIdentifier nid)
206− WithImplicitReconnect
207− msg
src/Control/Distributed/Process/Internal/Primitives.hs → distributed-process/src/Control/Distributed/Process/Internal/Primitives.hsrenamed+189−595
@@ -1,11 +1,3 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE RankNTypes #-}
3−{-# LANGUAGE DeriveDataTypeable #-}
4−{-# LANGUAGE DeriveFunctor #-}
5−{-# LANGUAGE ScopedTypeVariables #-}
6−{-# LANGUAGE ExistentialQuantification #-}
7−{-# LANGUAGE BangPatterns #-}
8−
9-- | Cloud Haskell primitives
1-- | Cloud Haskell primitives
10--
2--
11-- We define these in a separate module so that we don't have to rely on
3-- We define these in a separate module so that we don't have to rely on
@@ -13,7 +5,6 @@
13module Control.Distributed.Process.Internal.Primitives
5module Control.Distributed.Process.Internal.Primitives
14 ( -- * Basic messaging
6 ( -- * Basic messaging
15 send
7 send
16− , usend
17 , expect
8 , expect
18 -- * Channels
9 -- * Channels
19 , newChan
10 , newChan
@@ -21,12 +12,6 @@ module Control.Distributed.Process.Internal.Primitives
21 , receiveChan
12 , receiveChan
22 , mergePortsBiased
13 , mergePortsBiased
23 , mergePortsRR
14 , mergePortsRR
24− -- * Unsafe messaging variants
25− , unsafeSend
26− , unsafeUSend
27− , unsafeSendChan
28− , unsafeNSend
29− , unsafeNSendRemote
30 -- * Advanced messaging
15 -- * Advanced messaging
31 , Match
16 , Match
32 , receiveWait
17 , receiveWait
@@ -34,25 +19,10 @@ module Control.Distributed.Process.Internal.Primitives
34 , match
19 , match
35 , matchIf
20 , matchIf
36 , matchUnknown
21 , matchUnknown
22+ , AbstractMessage(..)
37 , matchAny
23 , matchAny
38 , matchAnyIf
24 , matchAnyIf
39 , matchChan
25 , matchChan
40− , matchSTM
41− , matchMessage
42− , matchMessageIf
43− , isEncoded
44− , wrapMessage
45− , unsafeWrapMessage
46− , unwrapMessage
47− , handleMessage
48− , handleMessageIf
49− , handleMessage_
50− , handleMessageIf_
51− , forward
52− , uforward
53− , delegate
54− , relay
55− , proxy
56 -- * Process management
26 -- * Process management
57 , terminate
27 , terminate
58 , ProcessTerminationException(..)
28 , ProcessTerminationException(..)
@@ -68,19 +38,13 @@ module Control.Distributed.Process.Internal.Primitives
68 , getSelfNode
38 , getSelfNode
69 , ProcessInfo(..)
39 , ProcessInfo(..)
70 , getProcessInfo
40 , getProcessInfo
71− , NodeStats(..)
72− , getNodeStats
73− , getLocalNodeStats
74 -- * Monitoring and linking
41 -- * Monitoring and linking
75 , link
42 , link
76 , unlink
43 , unlink
77 , monitor
44 , monitor
78 , unmonitor
45 , unmonitor
79− , unmonitorAsync
80 , withMonitor
46 , withMonitor
81− , withMonitor_
82 -- * Logging
47 -- * Logging
83− , SayMessage(..)
84 , say
48 , say
85 -- * Registry
49 -- * Registry
86 , register
50 , register
@@ -102,7 +66,6 @@ module Control.Distributed.Process.Internal.Primitives
102 , catches
66 , catches
103 , try
67 , try
104 , mask
68 , mask
105− , mask_
106 , onException
69 , onException
107 , bracket
70 , bracket
108 , bracket_
71 , bracket_
@@ -120,40 +83,31 @@ module Control.Distributed.Process.Internal.Primitives
120 -- * Reconnecting
83 -- * Reconnecting
121 , reconnect
84 , reconnect
122 , reconnectPort
85 , reconnectPort
123− -- * Internal Exports
86+ -- * Tracing/Debugging
124− , sendCtrlMsg
87+ , trace
125 ) where
88 ) where
126
89
127#if ! MIN_VERSION_base(4,6,0)
90#if ! MIN_VERSION_base(4,6,0)
128import Prelude hiding (catch)
91import Prelude hiding (catch)
129#endif
92#endif
130
93
131−import Data.Binary (Binary(..), Put, Get, decode)
94+import Data.Binary (decode)
132−import Data.Time.Clock (getCurrentTime, UTCTime(..))
95+import Data.Time.Clock (getCurrentTime)
133−import Data.Time.Calendar (Day(..))
134import Data.Time.Format (formatTime)
96import Data.Time.Format (formatTime)
135−#if MIN_VERSION_time(1,5,0)
136−import Data.Time.Format (defaultTimeLocale)
137−#else
138import System.Locale (defaultTimeLocale)
97import System.Locale (defaultTimeLocale)
139−#endif
140import System.Timeout (timeout)
98import System.Timeout (timeout)
141−import Control.Monad (when, void)
99+import Control.Monad (when)
142import Control.Monad.Reader (ask)
100import Control.Monad.Reader (ask)
143−import Control.Monad.IO.Class (liftIO)
101+import Control.Monad.IO.Class (MonadIO, liftIO)
144−import Control.Monad.Catch
102+import Control.Applicative ((<$>))
145− ( Exception
103+import Control.Exception (Exception(..), throw, throwIO, SomeException)
146− , SomeException
147− , throwM
148− , fromException
149− )
150−import qualified Control.Monad.Catch as Catch
104+import qualified Control.Exception as Ex (catch, mask, try)
151−import Control.Applicative
152import Control.Distributed.Process.Internal.StrictMVar
105import Control.Distributed.Process.Internal.StrictMVar
153 ( StrictMVar
106 ( StrictMVar
154 , modifyMVar
107 , modifyMVar
155 , modifyMVar_
108 , modifyMVar_
156 )
109 )
110+import Control.Concurrent.Chan (writeChan)
157import Control.Concurrent.STM
111import Control.Concurrent.STM
158 ( STM
112 ( STM
159 , TVar
113 , TVar
@@ -170,13 +124,9 @@ import Control.Distributed.Process.Internal.CQueue
170 )
124 )
171import Control.Distributed.Process.Serializable (Serializable, fingerprint)
125import Control.Distributed.Process.Serializable (Serializable, fingerprint)
172import Data.Accessor ((^.), (^:), (^=))
126import Data.Accessor ((^.), (^:), (^=))
173−import Control.Distributed.Static
127+import Control.Distributed.Static (Closure, Static)
174− ( Static
175− , Closure
176− )
177import Data.Rank1Typeable (Typeable)
128import Data.Rank1Typeable (Typeable)
178import qualified Control.Distributed.Static as Static (unstatic, unclosure)
129import qualified Control.Distributed.Static as Static (unstatic, unclosure)
179−import qualified Control.Distributed.Process.UnsafePrimitives as Unsafe
180import Control.Distributed.Process.Internal.Types
130import Control.Distributed.Process.Internal.Types
181 ( NodeId(..)
131 ( NodeId(..)
182 , ProcessId(..)
132 , ProcessId(..)
@@ -186,9 +136,8 @@ import Control.Distributed.Process.Internal.Types
186 , Message(..)
136 , Message(..)
187 , MonitorRef(..)
137 , MonitorRef(..)
188 , SpawnRef(..)
138 , SpawnRef(..)
139+ , NCMsg(..)
189 , ProcessSignal(..)
140 , ProcessSignal(..)
190− , NodeMonitorNotification(..)
191− , ProcessMonitorNotification(..)
192 , monitorCounter
141 , monitorCounter
193 , spawnCounter
142 , spawnCounter
194 , SendPort(..)
143 , SendPort(..)
@@ -199,7 +148,6 @@ import Control.Distributed.Process.Internal.Types
199 , SendPortId(..)
148 , SendPortId(..)
200 , Identifier(..)
149 , Identifier(..)
201 , ProcessExitException(..)
150 , ProcessExitException(..)
202− , DiedReason(..)
203 , DidUnmonitor(..)
151 , DidUnmonitor(..)
204 , DidUnlinkProcess(..)
152 , DidUnlinkProcess(..)
205 , DidUnlinkNode(..)
153 , DidUnlinkNode(..)
@@ -209,11 +157,9 @@ import Control.Distributed.Process.Internal.Types
209 , ProcessRegistrationException(..)
157 , ProcessRegistrationException(..)
210 , ProcessInfo(..)
158 , ProcessInfo(..)
211 , ProcessInfoNone(..)
159 , ProcessInfoNone(..)
212− , NodeStats(..)
213− , isEncoded
214 , createMessage
160 , createMessage
215− , createUnencodedMessage
161+ , runLocalProcess
216− , ImplicitReconnect( NoImplicitReconnect)
162+ , ImplicitReconnect(WithImplicitReconnect, NoImplicitReconnect)
217 , LocalProcessState
163 , LocalProcessState
218 , LocalSendPortId
164 , LocalSendPortId
219 , messageToPayload
165 , messageToPayload
@@ -223,22 +169,13 @@ import Control.Distributed.Process.Internal.Messaging
223 , sendBinary
169 , sendBinary
224 , sendPayload
170 , sendPayload
225 , disconnect
171 , disconnect
226− , sendCtrlMsg
227− )
228−import Control.Distributed.Process.Management.Internal.Types
229− ( MxEvent(..)
230− )
231−import Control.Distributed.Process.Management.Internal.Trace.Types
232− ( traceEvent
233 )
172 )
173+import qualified Control.Distributed.Process.Internal.Trace as Trace
234import Control.Distributed.Process.Internal.WeakTQueue
174import Control.Distributed.Process.Internal.WeakTQueue
235 ( newTQueueIO
175 ( newTQueueIO
236 , readTQueue
176 , readTQueue
237 , mkWeakTQueue
177 , mkWeakTQueue
238 )
178 )
239−import Prelude
240−
241−import Unsafe.Coerce
242
179
243--------------------------------------------------------------------------------
180--------------------------------------------------------------------------------
244-- Basic messaging --
181-- Basic messaging --
@@ -247,57 +184,14 @@ import Unsafe.Coerce
247-- | Send a message
184-- | Send a message
248send :: Serializable a => ProcessId -> a -> Process ()
185send :: Serializable a => ProcessId -> a -> Process ()
249-- This requires a lookup on every send. If we want to avoid that we need to
186-- This requires a lookup on every send. If we want to avoid that we need to
250−-- modify serializable to allow for stateful (IO) deserialization.
187+-- modify serializable to allow for stateful (IO) deserialization
251send them msg = do
188send them msg = do
252 proc <- ask
189 proc <- ask
253− let us = processId proc
254− node = processNode proc
255− nodeId = localNodeId node
256− destNode = (processNodeId them) in do
257− liftIO $ traceEvent (localEventBus node)
258− (MxSent them us (createUnencodedMessage msg))
259− if destNode == nodeId
260− then sendLocal them msg
261− else liftIO $ sendMessage (processNode proc)
190+ liftIO $ sendMessage (processNode proc)
262− (ProcessIdentifier (processId proc))
191+ (ProcessIdentifier (processId proc))
263− (ProcessIdentifier them)
192+ (ProcessIdentifier them)
264− NoImplicitReconnect
193+ NoImplicitReconnect
265− msg
194+ msg
266−
267−-- | /Unsafe/ variant of 'send'. This function makes /no/ attempt to serialize
268−-- and (in the case when the destination process resides on the same local
269−-- node) therefore ensure that the payload is fully evaluated before it is
270−-- delivered.
271−unsafeSend :: Serializable a => ProcessId -> a -> Process ()
272−unsafeSend = Unsafe.send
273−
274−-- | Send a message unreliably.
275−--
276−-- Unlike 'send', this function is insensitive to 'reconnect'. It will
277−-- try to send the message regardless of the history of connection failures
278−-- between the nodes.
279−--
280−-- Message passing with 'usend' is ordered for a given sender and receiver
281−-- if the messages arrive at all.
282−--
283−usend :: Serializable a => ProcessId -> a -> Process ()
284−usend them msg = do
285− proc <- ask
286− let there = processNodeId them
287− let (us, node) = (processId proc, processNode proc)
288− let msg' = wrapMessage msg
289− liftIO $ traceEvent (localEventBus node) (MxSent them us msg')
290− if localNodeId (processNode proc) == there
291− then sendLocal them msg
292− else sendCtrlMsg (Just there) $ UnreliableSend (processLocalId them)
293− (createMessage msg)
294−
295−-- | /Unsafe/ variant of 'usend'. This function makes /no/ attempt to serialize
296−-- the message when the destination process resides on the same local
297−-- node. Therefore, a local receiver would need to be prepared to cope with any
298−-- errors resulting from evaluation of the message.
299−unsafeUSend :: Serializable a => ProcessId -> a -> Process ()
300−unsafeUSend = Unsafe.usend
301
195
302-- | Wait for a message of a specific type
196-- | Wait for a message of a specific type
303expect :: forall a. Serializable a => Process a
197expect :: forall a. Serializable a => Process a
@@ -307,13 +201,7 @@ expect = receiveWait [match return]
307-- Channels --
201-- Channels --
308--------------------------------------------------------------------------------
202--------------------------------------------------------------------------------
309
203
310−-- | Create a new typed channel, bound to the calling Process
204+-- | Create a new typed channel
311−--
312−-- Note that the channel is bound to the lifecycle of the process that evaluates
313−-- this function, such that when it dies/exits, the channel will no longer
314−-- function, but will remain accessible. Thus reading from the ReceivePort will
315−-- fail silently thereafter, blocking indefinitely (unless a timeout is used).
316−--
317newChan :: Serializable a => Process (SendPort a, ReceivePort a)
205newChan :: Serializable a => Process (SendPort a, ReceivePort a)
318newChan = do
206newChan = do
319 proc <- ask
207 proc <- ask
@@ -341,26 +229,11 @@ newChan = do
341sendChan :: Serializable a => SendPort a -> a -> Process ()
229sendChan :: Serializable a => SendPort a -> a -> Process ()
342sendChan (SendPort cid) msg = do
230sendChan (SendPort cid) msg = do
343 proc <- ask
231 proc <- ask
344− let node = processNode proc
345− pid = processId proc
346− us = localNodeId node
347− them = processNodeId (sendPortProcessId cid) in do
348− liftIO $ traceEvent (localEventBus node) (MxSentToPort pid cid $ wrapMessage msg)
349− case them == us of
350− True -> sendChanLocal cid msg
351− False -> do
352− liftIO $ sendBinary node
232+ liftIO $ sendBinary (processNode proc)
353− (ProcessIdentifier pid)
233+ (ProcessIdentifier (processId proc))
354− (SendPortIdentifier cid)
234+ (SendPortIdentifier cid)
355− NoImplicitReconnect
235+ NoImplicitReconnect
356− msg
236+ msg
357−
358−-- | Send a message on a typed channel. This function makes /no/ attempt to
359−-- serialize and (in the case when the @ReceivePort@ resides on the same local
360−-- node) therefore ensure that the payload is fully evaluated before it is
361−-- delivered.
362−unsafeSendChan :: Serializable a => SendPort a -> a -> Process ()
363−unsafeSendChan = Unsafe.sendChan
364
237
365-- | Wait for a message on a typed channel
238-- | Wait for a message on a typed channel
366receiveChan :: Serializable a => ReceivePort a -> Process a
239receiveChan :: Serializable a => ReceivePort a -> Process a
@@ -405,17 +278,13 @@ mergePortsRR = \ps -> do
405
278
406-- | Opaque type used in 'receiveWait' and 'receiveTimeout'
279-- | Opaque type used in 'receiveWait' and 'receiveTimeout'
407newtype Match b = Match { unMatch :: MatchOn Message (Process b) }
280newtype Match b = Match { unMatch :: MatchOn Message (Process b) }
408− deriving (Functor)
409
281
410-- | Test the matches in order against each message in the queue
282-- | Test the matches in order against each message in the queue
411receiveWait :: [Match b] -> Process b
283receiveWait :: [Match b] -> Process b
412receiveWait ms = do
284receiveWait ms = do
413 queue <- processQueue <$> ask
285 queue <- processQueue <$> ask
414− mProc <- liftIO $ dequeue queue Blocking (map unMatch ms)
286+ Just proc <- liftIO $ dequeue queue Blocking (map unMatch ms)
415− case mProc of
416− Just proc' -> proc'
287+ proc
417− Nothing -> die $ "System Invariant Violation: CQueue.hs returned `Nothing` "
418− ++ "in the absence of a timeout value."
419
288
420-- | Like 'receiveWait' but with a timeout.
289-- | Like 'receiveWait' but with a timeout.
421--
290--
@@ -431,26 +300,9 @@ receiveTimeout t ms = do
431 Nothing -> return Nothing
300 Nothing -> return Nothing
432 Just proc -> Just <$> proc
301 Just proc -> Just <$> proc
433
302
434−-- | Match on a typed channel
435matchChan :: ReceivePort a -> (a -> Process b) -> Match b
303matchChan :: ReceivePort a -> (a -> Process b) -> Match b
436matchChan p fn = Match $ MatchChan (fmap fn (receiveSTM p))
304matchChan p fn = Match $ MatchChan (fmap fn (receiveSTM p))
437
305
438−-- | Match on an arbitrary STM action.
439−--
440−-- This rather unusaul /match primitive/ allows us to compose arbitrary STM
441−-- actions with checks against our process' mailbox and/or any typed channel
442−-- @ReceivePort@s we may hold.
443−--
444−-- This allows us to process multiple input streams /along with our mailbox/,
445−-- in just the same way that 'matchChan' supports checking both the mailbox
446−-- /and/ an arbitrary set of typed channels in one atomic transaction.
447−--
448−-- Note there are no ordering guarnatees with respect to these disparate input
449−-- sources.
450−--
451−matchSTM :: STM a -> (a -> Process b) -> Match b
452−matchSTM stm fn = Match $ MatchChan (fmap fn stm)
453−
454-- | Match against any message of the right type
306-- | Match against any message of the right type
455match :: forall a b. Serializable a => (a -> Process b) -> Match b
307match :: forall a b. Serializable a => (a -> Process b) -> Match b
456match = matchIf (const True)
308match = matchIf (const True)
@@ -458,251 +310,83 @@ match = matchIf (const True)
458-- | Match against any message of the right type that satisfies a predicate
310-- | Match against any message of the right type that satisfies a predicate
459matchIf :: forall a b. Serializable a => (a -> Bool) -> (a -> Process b) -> Match b
311matchIf :: forall a b. Serializable a => (a -> Bool) -> (a -> Process b) -> Match b
460matchIf c p = Match $ MatchMsg $ \msg ->
312matchIf c p = Match $ MatchMsg $ \msg ->
461− case messageFingerprint msg == fingerprint (undefined :: a) of
313+ case messageFingerprint msg == fingerprint (undefined :: a) of
462− False -> Nothing
463− True -> case msg of
464− (UnencodedMessage _ m) ->
465− let m' = unsafeCoerce m :: a in
466− case (c m') of
467− True -> Just (p m')
468− False -> Nothing
469− (EncodedMessage _ _) ->
470− if (c decoded) then Just (p decoded) else Nothing
314+ True | c decoded -> Just (p decoded)
471− where
315+ where
472− decoded :: a
316+ decoded :: a
473− -- Make sure the value is fully decoded so that we don't hang to
317+ -- Make sure the value is fully decoded so that we don't hang to
474− -- bytestrings when the process calling 'matchIf' doesn't process
318+ -- bytestrings when the process calling 'matchIf' doesn't process
475− -- the values immediately
319+ -- the values immediately
476− !decoded = decode (messageEncoding msg)
320+ !decoded = decode (messageEncoding msg)
477−
321+ _ -> Nothing
478−-- | Match against any message, regardless of the underlying (contained) type
479−matchMessage :: (Message -> Process Message) -> Match Message
480−matchMessage p = Match $ MatchMsg $ \msg -> Just (p msg)
481−
482−-- | Match against any message (regardless of underlying type) that satisfies a predicate
483−matchMessageIf :: (Message -> Bool) -> (Message -> Process Message) -> Match Message
484−matchMessageIf c p = Match $ MatchMsg $ \msg ->
485− case (c msg) of
486− True -> Just (p msg)
487− False -> Nothing
488−
489−-- | Forward a raw 'Message' to the given 'ProcessId'.
490−forward :: Message -> ProcessId -> Process ()
491−forward msg them = do
492− proc <- ask
493− let node = processNode proc
494− us = processId proc
495− nid = localNodeId node
496− destNode = (processNodeId them) in do
497− liftIO $ traceEvent (localEventBus node) (MxSent them us msg)
498− if destNode == nid
499− then sendCtrlMsg Nothing (LocalSend them msg)
500− else liftIO $ sendPayload (processNode proc)
501− (ProcessIdentifier (processId proc))
502− (ProcessIdentifier them)
503− NoImplicitReconnect
504− (messageToPayload msg)
505−
506−-- | Forward a raw 'Message' to the given 'ProcessId'.
507−--
508−-- Unlike 'forward', this function is insensitive to 'reconnect'. It will
509−-- try to send the message regardless of the history of connection failures
510−-- between the nodes.
511−uforward :: Message -> ProcessId -> Process ()
512−uforward msg them = do
513− proc <- ask
514− let node = processNode proc
515− us = processId proc
516− nid = localNodeId node
517− destNode = (processNodeId them) in do
518− liftIO $ traceEvent (localEventBus node) (MxSent them us msg)
519− if destNode == nid
520− then sendCtrlMsg Nothing (LocalSend them msg)
521− else sendCtrlMsg (Just destNode) $ UnreliableSend (processLocalId them) msg
522−
523−-- | Wrap a 'Serializable' value in a 'Message'. Note that 'Message's are
524−-- 'Serializable' - like the datum they contain - but also note, deserialising
525−-- such a 'Message' will yield a 'Message', not the type within it! To obtain the
526−-- wrapped datum, use 'unwrapMessage' or 'handleMessage' with a specific type.
527−--
528−-- @
529−-- do
530−-- self <- getSelfPid
531−-- send self (wrapMessage "blah")
532−-- Nothing <- expectTimeout 1000000 :: Process (Maybe String)
533−-- (Just m) <- expectTimeout 1000000 :: Process (Maybe Message)
534−-- (Just "blah") <- unwrapMessage m :: Process (Maybe String)
535−-- @
536−--
537−wrapMessage :: Serializable a => a -> Message
538−wrapMessage = createUnencodedMessage -- see [note Serializable UnencodedMessage]
539−
540−-- | This is the /unsafe/ variant of 'wrapMessage'. See
541−-- "Control.Distributed.Process.UnsafePrimitives" for details.
542−unsafeWrapMessage :: Serializable a => a -> Message
543−unsafeWrapMessage = Unsafe.wrapMessage
544−
545−-- [note Serializable UnencodedMessage]
546−-- if we attempt to serialise an UnencodedMessage, it will be converted to an
547−-- EncodedMessage first, so we're ok here. See 'messageToPayload' in
548−-- Primitives.hs for the gory details
549−
550−-- | Attempt to unwrap a raw 'Message'.
551−-- If the type of the decoded message payload matches the expected type, the
552−-- value will be returned with @Just@, otherwise @Nothing@ indicates the types
553−-- do not match.
554−--
555−-- This expression, for example, will evaluate to @Nothing@
556−-- > unwrapMessage (wrapMessage "foobar") :: Process (Maybe Int)
557−--
558−-- Whereas this expression, will yield @Just "foo"@
559−-- > unwrapMessage (wrapMessage "foo") :: Process (Maybe String)
560−--
561−unwrapMessage :: forall m a. (Monad m, Serializable a) => Message -> m (Maybe a)
562−unwrapMessage msg =
563− case messageFingerprint msg == fingerprint (undefined :: a) of
564− False -> return Nothing :: m (Maybe a)
565− True -> case msg of
566− (UnencodedMessage _ ms) ->
567− let ms' = unsafeCoerce ms :: a
568− in return (Just ms')
569− (EncodedMessage _ _) ->
570− return (Just (decoded))
571− where
572− decoded :: a -- note [decoding]
573− !decoded = decode (messageEncoding msg)
574−
575−-- | Attempt to handle a raw 'Message'.
576−-- If the type of the message matches the type of the first argument to
577−-- the supplied expression, then the message will be decoded and the expression
578−-- evaluated against its value. If this runtime type checking fails however,
579−-- @Nothing@ will be returned to indicate the fact. If the check succeeds and
580−-- evaluation proceeds, the resulting value with be wrapped with @Just@.
581−--
582−-- Intended for use in `catchesExit` and `matchAny` primitives.
583−--
584−handleMessage :: forall m a b. (Monad m, Serializable a)
585− => Message -> (a -> m b) -> m (Maybe b)
586−handleMessage msg proc = handleMessageIf msg (const True) proc
587−
588−-- | Conditionally handle a raw 'Message'.
589−-- If the predicate @(a -> Bool)@ evaluates to @True@, invokes the supplied
590−-- handler, other returns @Nothing@ to indicate failure. See 'handleMessage'
591−-- for further information about runtime type checking.
592−--
593−handleMessageIf :: forall m a b . (Monad m, Serializable a)
594− => Message
595− -> (a -> Bool)
596− -> (a -> m b)
597− -> m (Maybe b)
598−handleMessageIf msg c proc = do
599− case messageFingerprint msg == fingerprint (undefined :: a) of
600− False -> return Nothing :: m (Maybe b)
601− True -> case msg of
602− (UnencodedMessage _ ms) ->
603− let ms' = unsafeCoerce ms :: a in
604− case (c ms') of
605− True -> do { r <- proc ms'; return (Just r) }
606− False -> return Nothing :: m (Maybe b)
607− (EncodedMessage _ _) ->
608− case (c decoded) of
609− True -> do { r <- proc (decoded :: a); return (Just r) }
610− False -> return Nothing :: m (Maybe b)
611− where
612− decoded :: a -- note [decoding]
613− !decoded = decode (messageEncoding msg)
614−
615−-- | As 'handleMessage' but ignores result, which is useful if you don't
616−-- care whether or not the handler succeeded.
617−--
618−handleMessage_ :: forall m a . (Monad m, Serializable a)
619− => Message -> (a -> m ()) -> m ()
620−handleMessage_ msg proc = handleMessageIf_ msg (const True) proc
621
322
622−-- | Conditional version of 'handleMessage_'.
323+-- | Represents a received message and provides two basic operations on it.
623−--
324+data AbstractMessage = AbstractMessage {
325+ forward :: ProcessId -> Process () -- ^ forward the message to @ProcessId@
624−handleMessageIf_ :: forall m a . (Monad m, Serializable a)
326+ , maybeHandleMessage :: forall a b. (Serializable a)
625− => Message
327+ => (a -> Process b) -> Process (Maybe b) {- ^ Handle the message.
626− -> (a -> Bool)
328+ If the type of the message matches the type of the first argument to
627− -> (a -> m ())
329+ the supplied expression, then the expression will be evaluated against
628− -> m ()
330+ it. If this runtime type checking fails, then @Nothing@ will be returned
629−handleMessageIf_ msg c proc = handleMessageIf msg c proc >> return ()
331+ to indicate the fact. If the check succeeds and evaluation proceeds
332+ however, the resulting value with be wrapped with @Just@.
333+ -}
334+ }
630
335
631-- | Match against an arbitrary message. 'matchAny' removes the first available
336-- | Match against an arbitrary message. 'matchAny' removes the first available
632−-- message from the process mailbox. To handle arbitrary /raw/ messages once
337+-- message from the process mailbox, and via the 'AbstractMessage' type,
633−-- removed from the mailbox, see 'handleMessage' and 'unwrapMessage'.
338+-- supports forwarding /or/ handling the message /if/ it is of the correct
339+-- type. If /not/ of the right type, then the 'AbstractMessage'
340+-- @maybeHandleMessage@ function will not evaluate the supplied expression,
341+-- /but/ the message will still have been removed from the process mailbox!
634−--
342+--
635−matchAny :: forall b. (Message -> Process b) -> Match b
343+matchAny :: forall b. (AbstractMessage -> Process b) -> Match b
636−matchAny p = Match $ MatchMsg $ \msg -> Just (p msg)
344+matchAny p = Match $ MatchMsg $ Just . p . abstract
637−
345+
638−-- | Match against an arbitrary message. Intended for use with 'handleMessage'
346+-- | Match against an arbitrary message. 'matchAnyIf' will /only/ remove the
639−-- and 'unwrapMessage', this function /only/ removes a message from the process
640−-- mailbox, /if/ the supplied condition matches. The success (or failure) of
347+-- message from the process mailbox, /if/ the supplied condition matches. The
641−-- runtime type checks deferred to @handleMessage@ and friends is irrelevant
348+-- success (or failure) of runtime type checks in @maybeHandleMessage@ does not
642−-- here, i.e., if the condition evaluates to @True@ then the message will
349+-- count here, i.e., if the condition evaluates to @True@ then the message will
643-- be removed from the process mailbox and decoded, but that does /not/
350-- be removed from the process mailbox and decoded, but that does /not/
644−-- guarantee that an expression passed to @handleMessage@ will pass the
351+-- guarantee that an expression passed to @maybeHandleMessage@ will pass the
645−-- runtime type checks and therefore be evaluated.
352+-- runtime type checks and therefore be evaluated. If the types do not match
646−--
353+-- up, then @maybeHandleMessage@ returns 'Nothing'.
647matchAnyIf :: forall a b. (Serializable a)
354matchAnyIf :: forall a b. (Serializable a)
648 => (a -> Bool)
355 => (a -> Bool)
649− -> (Message -> Process b)
356+ -> (AbstractMessage -> Process b)
650 -> Match b
357 -> Match b
651matchAnyIf c p = Match $ MatchMsg $ \msg ->
358matchAnyIf c p = Match $ MatchMsg $ \msg ->
652− case messageFingerprint msg == fingerprint (undefined :: a) of
359+ case messageFingerprint msg == fingerprint (undefined :: a) of
653− True | check -> Just (p msg)
360+ True | c decoded -> Just (p (abstract msg))
654 where
361 where
655− check :: Bool
656− !check =
657− case msg of
658− (EncodedMessage _ _) -> c decoded
659− (UnencodedMessage _ m') -> c (unsafeCoerce m')
660−
661− decoded :: a -- note [decoding]
362+ decoded :: a
363+ -- Make sure the value is fully decoded so that we don't hang to
364+ -- bytestrings when the calling process doesn't evaluate immediately
662 !decoded = decode (messageEncoding msg)
365 !decoded = decode (messageEncoding msg)
663 _ -> Nothing
366 _ -> Nothing
664
367
665−{- note [decoding]
368+abstract :: Message -> AbstractMessage
666−For an EncodedMessage, we need to ensure the value is fully decoded so that
369+abstract msg = AbstractMessage {
667−we don't hang to bytestrings if the calling process doesn't evaluate
370+ forward = \them -> do
668−immediately. For UnencodedMessage we know (because the fingerprint comparison
371+ proc <- ask
669−succeeds) that unsafeCoerce will not fail.
372+ liftIO $ sendPayload (processNode proc)
373+ (ProcessIdentifier (processId proc))
374+ (ProcessIdentifier them)
375+ NoImplicitReconnect
376+ (messageToPayload msg)
377+ , maybeHandleMessage = \(proc :: (a -> Process b)) -> do
378+ case messageFingerprint msg == fingerprint (undefined :: a) of
379+ True -> do { r <- proc (decoded :: a); return (Just r) }
380+ where
381+ decoded :: a
382+ !decoded = decode (messageEncoding msg)
383+ _ -> return Nothing
670−-}
384+ }
671
385
672-- | Remove any message from the queue
386-- | Remove any message from the queue
673matchUnknown :: Process b -> Match b
387matchUnknown :: Process b -> Match b
674matchUnknown p = Match $ MatchMsg (const (Just p))
388matchUnknown p = Match $ MatchMsg (const (Just p))
675
389
676−-- | Receives messages and forwards them to @pid@ if @p msg == True@.
677−delegate :: ProcessId -> (Message -> Bool) -> Process ()
678−delegate pid p = do
679− receiveWait [
680− matchAny (\m -> case (p m) of
681− True -> forward m pid
682− False -> return ())
683− ]
684− delegate pid p
685−
686−-- | A straight relay that forwards all messages to the supplied pid.
687−relay :: ProcessId -> Process ()
688−relay !pid = receiveWait [ matchAny (\m -> forward m pid) ] >> relay pid
689−
690−-- | Proxies @pid@ and forwards messages whenever @proc@ evaluates to @True@.
691−-- Unlike 'delegate' the predicate @proc@ runs in the 'Process' monad, allowing
692−-- for richer proxy behaviour. If @proc@ returns @False@ or the /runtime type check/
693−-- fails, no action is taken and the proxy process will continue running.
694−proxy :: Serializable a => ProcessId -> (a -> Process Bool) -> Process ()
695−proxy pid proc = do
696− receiveWait [
697− matchAny (\m -> do
698− next <- handleMessage m proc
699− case next of
700− Just True -> forward m pid
701− Just False -> return () -- explicitly ignored
702− Nothing -> return ()) -- un-routable
703− ]
704− proxy pid proc
705−
706--------------------------------------------------------------------------------
390--------------------------------------------------------------------------------
707-- Process management --
391-- Process management --
708--------------------------------------------------------------------------------
392--------------------------------------------------------------------------------
@@ -715,17 +399,14 @@ instance Exception ProcessTerminationException
715
399
716-- | Terminate immediately (throws a ProcessTerminationException)
400-- | Terminate immediately (throws a ProcessTerminationException)
717terminate :: Process a
401terminate :: Process a
718−terminate = throwM ProcessTerminationException
402+terminate = liftIO $ throwIO ProcessTerminationException
719
403
720-- [Issue #110]
404-- [Issue #110]
721-- | Die immediately - throws a 'ProcessExitException' with the given @reason@.
405-- | Die immediately - throws a 'ProcessExitException' with the given @reason@.
722die :: Serializable a => a -> Process b
406die :: Serializable a => a -> Process b
723die reason = do
407die reason = do
724−-- NOTE: We do /not/ want to use UnencodedMessage here, as the exception
725−-- could be decoded by a handler passed to 'catchExit', re-thrown or even
726−-- passed to another process via the tracing mechanism.
727 pid <- getSelfPid
408 pid <- getSelfPid
728− throwM (ProcessExitException pid (createMessage reason))
409+ liftIO $ throwIO (ProcessExitException pid (createMessage reason))
729
410
730-- | Forceful request to kill a process. Where 'exit' provides an exception
411-- | Forceful request to kill a process. Where 'exit' provides an exception
731-- that can be caught and handled, 'kill' throws an unexposed exception type
412-- that can be caught and handled, 'kill' throws an unexposed exception type
@@ -758,12 +439,12 @@ catchExit :: forall a b . (Show a, Serializable a)
758 => Process b
439 => Process b
759 -> (ProcessId -> a -> Process b)
440 -> (ProcessId -> a -> Process b)
760 -> Process b
441 -> Process b
761−catchExit act exitHandler = Catch.catch act handleExit
442+catchExit act exitHandler = catch act handleExit
762 where
443 where
763 handleExit ex@(ProcessExitException from msg) =
444 handleExit ex@(ProcessExitException from msg) =
764 if messageFingerprint msg == fingerprint (undefined :: a)
445 if messageFingerprint msg == fingerprint (undefined :: a)
765 then exitHandler from decoded
446 then exitHandler from decoded
766− else throwM ex
447+ else liftIO $ throwIO ex
767 where
448 where
768 decoded :: a
449 decoded :: a
769 -- Make sure the value is fully decoded so that we don't hang to
450 -- Make sure the value is fully decoded so that we don't hang to
@@ -773,23 +454,23 @@ catchExit act exitHandler = Catch.catch act handleExit
773-- | Lift 'Control.Exception.catches' (almost).
454-- | Lift 'Control.Exception.catches' (almost).
774--
455--
775-- As 'ProcessExitException' stores the exit @reason@ as a typed, encoded
456-- As 'ProcessExitException' stores the exit @reason@ as a typed, encoded
776−-- message, a handler must accept inputs of the expected type. In order to
457+-- message, a handler must accept an input of the expected type. In order to
777-- handle a list of potentially different handlers (and therefore input types),
458-- handle a list of potentially different handlers (and therefore input types),
778−-- a handler passed to 'catchesExit' must accept 'Message' and return
459+-- a handler passed to 'catchesExit' must accept 'AbstractMessage' and return
779-- @Maybe@ (i.e., @Just p@ if it handled the exit reason, otherwise @Nothing@).
460-- @Maybe@ (i.e., @Just p@ if it handled the exit reason, otherwise @Nothing@).
780--
461--
781−-- See 'maybeHandleMessage' and 'Message' for more details.
462+-- See 'maybeHandleMessage' and 'AsbtractMessage' for more details.
782catchesExit :: Process b
463catchesExit :: Process b
783− -> [(ProcessId -> Message -> (Process (Maybe b)))]
464+ -> [(ProcessId -> AbstractMessage -> (Process (Maybe b)))]
784 -> Process b
465 -> Process b
785−catchesExit act handlers = Catch.catch act ((flip handleExit) handlers)
466+catchesExit act handlers = catch act ((flip handleExit) handlers)
786 where
467 where
787 handleExit :: ProcessExitException
468 handleExit :: ProcessExitException
788− -> [(ProcessId -> Message -> Process (Maybe b))]
469+ -> [(ProcessId -> AbstractMessage -> Process (Maybe b))]
789 -> Process b
470 -> Process b
790− handleExit ex [] = throwM ex
471+ handleExit ex [] = liftIO $ throwIO ex
791 handleExit ex@(ProcessExitException from msg) (h:hs) = do
472 handleExit ex@(ProcessExitException from msg) (h:hs) = do
792− r <- h from msg
473+ r <- h from (abstract msg)
793 case r of
474 case r of
794 Nothing -> handleExit ex hs
475 Nothing -> handleExit ex hs
795 Just p -> return p
476 Just p -> return p
@@ -802,31 +483,6 @@ getSelfPid = processId <$> ask
802getSelfNode :: Process NodeId
483getSelfNode :: Process NodeId
803getSelfNode = localNodeId . processNode <$> ask
484getSelfNode = localNodeId . processNode <$> ask
804
485
805−
806−-- | Get statistics about the specified node
807−getNodeStats :: NodeId -> Process (Either DiedReason NodeStats)
808−getNodeStats nid = do
809− selfNode <- getSelfNode
810− if nid == selfNode
811− then Right `fmap` getLocalNodeStats -- optimisation
812− else getNodeStatsRemote selfNode
813− where
814− getNodeStatsRemote :: NodeId -> Process (Either DiedReason NodeStats)
815− getNodeStatsRemote selfNode = do
816− sendCtrlMsg (Just nid) $ GetNodeStats selfNode
817− bracket (monitorNode nid) unmonitor $ \mRef ->
818− receiveWait [ match (\(stats :: NodeStats) -> return $ Right stats)
819− , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef)
820− (\(NodeMonitorNotification _ _ dr) -> return $ Left dr)
821− ]
822−
823−-- | Get statistics about our local node
824−getLocalNodeStats :: Process NodeStats
825−getLocalNodeStats = do
826− self <- getSelfNode
827− sendCtrlMsg Nothing $ GetNodeStats self
828− receiveWait [ match (\(stats :: NodeStats) -> return stats) ]
829−
830-- | Get information about the specified process
486-- | Get information about the specified process
831getProcessInfo :: ProcessId -> Process (Maybe ProcessInfo)
487getProcessInfo :: ProcessId -> Process (Maybe ProcessInfo)
832getProcessInfo pid =
488getProcessInfo pid =
@@ -896,19 +552,12 @@ monitor = monitor' . ProcessIdentifier
896-- @withMonitor@ returns, there might still be unreceived monitor
552-- @withMonitor@ returns, there might still be unreceived monitor
897-- messages in the queue.
553-- messages in the queue.
898--
554--
899−withMonitor :: ProcessId -> (MonitorRef -> Process a) -> Process a
555+withMonitor :: ProcessId -> Process a -> Process a
900−withMonitor pid = bracket (monitor pid) unmonitor
556+withMonitor pid code = bracket (monitor pid) unmonitor (\_ -> code)
901−
557+ -- unmonitor blocks waiting for the response, so there's a possibility
902−-- | Establishes temporary monitoring of another process.
558+ -- that an exception might interrupt withMonitor before the unmonitor
903−--
559+ -- has completed. I think that's better than making the unmonitor
904−-- @withMonitor_ pid code@ sets up monitoring of @pid@ for the duration
560+ -- uninterruptible.
905−-- of @code@. Note: although monitoring is no longer active when
906−-- @withMonitor_@ returns, there might still be unreceived monitor
907−-- messages in the queue.
908−--
909−-- Since 0.6.1
910−withMonitor_ :: ProcessId -> Process a -> Process a
911−withMonitor_ p = withMonitor p . const
912
561
913-- | Remove a link
562-- | Remove a link
914--
563--
@@ -946,21 +595,12 @@ unlinkPort sport = do
946-- | Remove a monitor
595-- | Remove a monitor
947--
596--
948-- This has the same synchronous/asynchronous nature as 'unlink'.
597-- This has the same synchronous/asynchronous nature as 'unlink'.
949−--
950−-- ProcessMonitorNotification messages for the given MonitorRef are removed from
951−-- the mailbox.
952unmonitor :: MonitorRef -> Process ()
598unmonitor :: MonitorRef -> Process ()
953unmonitor ref = do
599unmonitor ref = do
954 unmonitorAsync ref
600 unmonitorAsync ref
955 receiveWait [ matchIf (\(DidUnmonitor ref') -> ref' == ref)
601 receiveWait [ matchIf (\(DidUnmonitor ref') -> ref' == ref)
956 (\_ -> return ())
602 (\_ -> return ())
957 ]
603 ]
958− -- Discard the notification if any. With the current NC implementation at most
959− -- one notification is in the mailbox for any given ref.
960− void $ receiveTimeout 0
961− [ matchIf (\(ProcessMonitorNotification ref' _ _) -> ref' == ref)
962− (const $ return ())
963− ]
964
604
965--------------------------------------------------------------------------------
605--------------------------------------------------------------------------------
966-- Exception handling --
606-- Exception handling --
@@ -968,56 +608,60 @@ unmonitor ref = do
968
608
969-- | Lift 'Control.Exception.catch'
609-- | Lift 'Control.Exception.catch'
970catch :: Exception e => Process a -> (e -> Process a) -> Process a
610catch :: Exception e => Process a -> (e -> Process a) -> Process a
971−catch = Catch.catch
611+catch p h = do
972−{-# DEPRECATED catch "Use Control.Monad.Catch.catch instead" #-}
612+ lproc <- ask
613+ liftIO $ Ex.catch (runLocalProcess lproc p) (runLocalProcess lproc . h)
973
614
974-- | Lift 'Control.Exception.try'
615-- | Lift 'Control.Exception.try'
975try :: Exception e => Process a -> Process (Either e a)
616try :: Exception e => Process a -> Process (Either e a)
976−try = Catch.try
617+try p = do
977−{-# DEPRECATED try "Use Control.Monad.Catch.try instead" #-}
618+ lproc <- ask
619+ liftIO $ Ex.try (runLocalProcess lproc p)
978
620
979-- | Lift 'Control.Exception.mask'
621-- | Lift 'Control.Exception.mask'
980mask :: ((forall a. Process a -> Process a) -> Process b) -> Process b
622mask :: ((forall a. Process a -> Process a) -> Process b) -> Process b
981−mask = Catch.mask
623+mask p = do
982−{-# DEPRECATED mask "Use Control.Monad.Catch.mask_ instead" #-}
624+ lproc <- ask
983−
625+ liftIO $ Ex.mask $ \restore ->
984−-- | Lift 'Control.Exception.mask_'
626+ runLocalProcess lproc (p (liftRestore lproc restore))
985−mask_ :: Process a -> Process a
627+ where
986−mask_ = Catch.mask_
628+ liftRestore :: LocalProcess -> (forall a. IO a -> IO a) -> (forall a. Process a -> Process a)
987−{-# DEPRECATED mask_ "Use Control.Monad.Catch.mask_ instead" #-}
629+ liftRestore lproc restoreIO = liftIO . restoreIO . runLocalProcess lproc
988
630
989-- | Lift 'Control.Exception.onException'
631-- | Lift 'Control.Exception.onException'
990onException :: Process a -> Process b -> Process a
632onException :: Process a -> Process b -> Process a
991−onException = Catch.onException
633+onException p what = p `catch` \e -> do _ <- what
992−{-# DEPRECATED onException "Use Control.Monad.Catch.onException instead" #-}
634+ liftIO $ throwIO (e :: SomeException)
993
635
994-- | Lift 'Control.Exception.bracket'
636-- | Lift 'Control.Exception.bracket'
995bracket :: Process a -> (a -> Process b) -> (a -> Process c) -> Process c
637bracket :: Process a -> (a -> Process b) -> (a -> Process c) -> Process c
996−bracket = Catch.bracket
638+bracket before after thing =
997−{-# DEPRECATED bracket "Use Control.Monad.Catch.bracket instead" #-}
639+ mask $ \restore -> do
640+ a <- before
641+ r <- restore (thing a) `onException` after a
642+ _ <- after a
643+ return r
998
644
999-- | Lift 'Control.Exception.bracket_'
645-- | Lift 'Control.Exception.bracket_'
1000bracket_ :: Process a -> Process b -> Process c -> Process c
646bracket_ :: Process a -> Process b -> Process c -> Process c
1001−bracket_ = Catch.bracket_
647+bracket_ before after thing = bracket before (const after) (const thing)
1002−{-# DEPRECATED bracket_ "Use Control.Monad.Catch.bracket_ instead" #-}
1003
648
1004-- | Lift 'Control.Exception.finally'
649-- | Lift 'Control.Exception.finally'
1005finally :: Process a -> Process b -> Process a
650finally :: Process a -> Process b -> Process a
1006−finally = Catch.finally
651+finally a sequel = bracket_ (return ()) sequel a
1007−{-# DEPRECATED finally "Use Control.Monad.Catch.finally instead" #-}
1008
652
1009-- | You need this when using 'catches'
653-- | You need this when using 'catches'
1010data Handler a = forall e . Exception e => Handler (e -> Process a)
654data Handler a = forall e . Exception e => Handler (e -> Process a)
1011
655
1012instance Functor Handler where
656instance Functor Handler where
1013− fmap f (Handler h) = Handler (fmap f . h)
657+ fmap f (Handler h) = Handler (fmap f . h)
1014
658
1015-- | Lift 'Control.Exception.catches'
659-- | Lift 'Control.Exception.catches'
1016catches :: Process a -> [Handler a] -> Process a
660catches :: Process a -> [Handler a] -> Process a
1017−catches proc handlers = proc `Catch.catch` catchesHandler handlers
661+catches proc handlers = proc `catch` catchesHandler handlers
1018
662
1019catchesHandler :: [Handler a] -> SomeException -> Process a
663catchesHandler :: [Handler a] -> SomeException -> Process a
1020−catchesHandler handlers e = foldr tryHandler (throwM e) handlers
664+catchesHandler handlers e = foldr tryHandler (throw e) handlers
1021 where tryHandler (Handler handler) res
665 where tryHandler (Handler handler) res
1022 = case fromException e of
666 = case fromException e of
1023 Just e' -> handler e'
667 Just e' -> handler e'
@@ -1037,10 +681,7 @@ expectTimeout n = receiveTimeout n [match return]
1037spawnAsync :: NodeId -> Closure (Process ()) -> Process SpawnRef
681spawnAsync :: NodeId -> Closure (Process ()) -> Process SpawnRef
1038spawnAsync nid proc = do
682spawnAsync nid proc = do
1039 spawnRef <- getSpawnRef
683 spawnRef <- getSpawnRef
1040− node <- getSelfNode
1041− if nid == node
1042− then sendCtrlMsg Nothing $ Spawn proc spawnRef
1043− else sendCtrlMsg (Just nid) $ Spawn proc spawnRef
684+ sendCtrlMsg (Just nid) $ Spawn proc spawnRef
1044 return spawnRef
685 return spawnRef
1045
686
1046-- | Monitor a node (asynchronous)
687-- | Monitor a node (asynchronous)
@@ -1086,60 +727,27 @@ unlinkPortAsync (SendPort cid) =
1086-- Logging --
727-- Logging --
1087--------------------------------------------------------------------------------
728--------------------------------------------------------------------------------
1088
729
1089−data SayMessage = SayMessage { sayTime :: UTCTime
1090− , sayProcess :: ProcessId
1091− , sayMessage :: String }
1092− deriving (Typeable)
1093−
1094−-- There is sadly no Show UTCTime instance
1095−instance Show SayMessage where
1096− showsPrec p msg =
1097− showParen (p >= 11)
1098− $ showString "SayMessage "
1099− . showString (formatTime defaultTimeLocale "%c" (sayTime msg))
1100− . showChar ' '
1101− . showsPrec 11 (sayProcess msg) . showChar ' '
1102− . showsPrec 11 (sayMessage msg) . showChar ' '
1103−
1104−instance Binary SayMessage where
1105− put s = do
1106− putUTCTime (sayTime s)
1107− put (sayProcess s)
1108− put (sayMessage s)
1109− get = SayMessage <$> getUTCTime <*> get <*> get
1110−
1111−-- Sadly there is no Binary UTCTime instance
1112−putUTCTime :: UTCTime -> Put
1113−putUTCTime (UTCTime (ModifiedJulianDay day) tod) = do
1114− put day
1115− put (toRational tod)
1116−
1117−getUTCTime :: Get UTCTime
1118−getUTCTime = do
1119− day <- get
1120− tod <- get
1121− return $! UTCTime (ModifiedJulianDay day)
1122− (fromRational tod)
1123−
1124-- | Log a string
730-- | Log a string
1125--
731--
1126−-- @say message@ sends a message of type 'SayMessage' with the current time and
732+-- @say message@ sends a message (time, pid of the current process, message)
1127−-- 'ProcessId' of the current process to the process registered as @logger@. By
733+-- to the process registered as 'logger'. By default, this process simply
1128−-- default, this process simply sends the string to @stderr@. Individual Cloud
734+-- sends the string to 'stderr'. Individual Cloud Haskell backends might
1129−-- Haskell backends might replace this with a different logger process, however.
735+-- replace this with a different logger process, however.
1130say :: String -> Process ()
736say :: String -> Process ()
1131say string = do
737say string = do
1132 now <- liftIO getCurrentTime
738 now <- liftIO getCurrentTime
1133 us <- getSelfPid
739 us <- getSelfPid
1134− nsend "logger" (SayMessage now us string)
740+ nsend "logger" (formatTime defaultTimeLocale "%c" now, us, string)
1135
741
1136--------------------------------------------------------------------------------
742--------------------------------------------------------------------------------
1137-- Registry --
743-- Registry --
1138--------------------------------------------------------------------------------
744--------------------------------------------------------------------------------
1139
745
1140−-- | Register a process with the local registry (synchronous). The name must not
746+-- | Register a process with the local registry (asynchronous).
747+-- This version will wait until a response is gotten from the
748+-- management process. The name must not already be registered.
1141−-- already be registered. The process need not be on this node. A bad
749+-- The process need not be on this node.
1142−-- registration will result in a 'ProcessRegistrationException'
750+-- A bad registration will result in a 'ProcessRegistrationException'
1143--
751--
1144-- The process to be registered does not have to be local itself.
752-- The process to be registered does not have to be local itself.
1145register :: String -> ProcessId -> Process ()
753register :: String -> ProcessId -> Process ()
@@ -1154,10 +762,9 @@ registerImpl :: Bool -> String -> ProcessId -> Process ()
1154registerImpl force label pid = do
762registerImpl force label pid = do
1155 mynid <- getSelfNode
763 mynid <- getSelfNode
1156 sendCtrlMsg Nothing (Register label mynid (Just pid) force)
764 sendCtrlMsg Nothing (Register label mynid (Just pid) force)
1157− receiveWait
1158− [ matchIf (\(RegisterReply label' _ _) -> label == label')
765+ receiveWait [ matchIf (\(RegisterReply label' _) -> label == label')
1159− (\(RegisterReply _ ok owner) -> handleRegistrationReply label ok owner)
766+ (\(RegisterReply _ ok) -> handleRegistrationReply label ok)
1160− ]
767+ ]
1161
768
1162-- | Register a process with a remote registry (asynchronous).
769-- | Register a process with a remote registry (asynchronous).
1163--
770--
@@ -1166,10 +773,8 @@ registerImpl force label pid = do
1166--
773--
1167-- See comments in 'whereisRemoteAsync'
774-- See comments in 'whereisRemoteAsync'
1168registerRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
775registerRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
1169−registerRemoteAsync nid label pid = do
776+registerRemoteAsync nid label pid =
1170− here <- getSelfNode
1171− sendCtrlMsg (if nid == here then Nothing else Just nid)
1172− (Register label nid (Just pid) False)
777+ sendCtrlMsg (Just nid) (Register label nid (Just pid) False)
1173
778
1174reregisterRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
779reregisterRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
1175reregisterRemoteAsync nid label pid =
780reregisterRemoteAsync nid label pid =
@@ -1182,17 +787,16 @@ unregister :: String -> Process ()
1182unregister label = do
787unregister label = do
1183 mynid <- getSelfNode
788 mynid <- getSelfNode
1184 sendCtrlMsg Nothing (Register label mynid Nothing False)
789 sendCtrlMsg Nothing (Register label mynid Nothing False)
1185− receiveWait
1186− [ matchIf (\(RegisterReply label' _ _) -> label == label')
790+ receiveWait [ matchIf (\(RegisterReply label' _) -> label == label')
1187− (\(RegisterReply _ ok owner) -> handleRegistrationReply label ok owner)
791+ (\(RegisterReply _ ok) -> handleRegistrationReply label ok)
1188− ]
792+ ]
1189
793
1190-- | Deal with the result from an attempted registration or unregistration
794-- | Deal with the result from an attempted registration or unregistration
1191-- by throwing an exception if necessary
795-- by throwing an exception if necessary
1192−handleRegistrationReply :: String -> Bool -> Maybe ProcessId -> Process ()
796+handleRegistrationReply :: String -> Bool -> Process ()
1193−handleRegistrationReply label ok owner =
797+handleRegistrationReply label ok =
1194 when (not ok) $
798 when (not ok) $
1195− throwM $ ProcessRegistrationException label owner
799+ liftIO $ throwIO $ ProcessRegistrationException label
1196
800
1197-- | Remove a process from a remote registry (asynchronous).
801-- | Remove a process from a remote registry (asynchronous).
1198--
802--
@@ -1221,45 +825,18 @@ whereis label = do
1221-- use 'monitorNode' and take appropriate action when you receive a
825-- use 'monitorNode' and take appropriate action when you receive a
1222-- 'NodeMonitorNotification').
826-- 'NodeMonitorNotification').
1223whereisRemoteAsync :: NodeId -> String -> Process ()
827whereisRemoteAsync :: NodeId -> String -> Process ()
1224−whereisRemoteAsync nid label = do
828+whereisRemoteAsync nid label =
1225− here <- getSelfNode
1226− sendCtrlMsg (if nid == here then Nothing else Just nid) (WhereIs label)
829+ sendCtrlMsg (Just nid) (WhereIs label)
1227
830
1228-- | Named send to a process in the local registry (asynchronous)
831-- | Named send to a process in the local registry (asynchronous)
1229nsend :: Serializable a => String -> a -> Process ()
832nsend :: Serializable a => String -> a -> Process ()
1230−nsend label msg = do
833+nsend label msg =
1231− proc <- ask
1232− let msg' = createUnencodedMessage msg
1233− liftIO $ traceEvent (localEventBus (processNode proc))
1234− (MxSentToName label (processId proc) msg')
1235− sendCtrlMsg Nothing (NamedSend label msg')
834+ sendCtrlMsg Nothing (NamedSend label (createMessage msg))
1236−
1237−-- | Named send to a process in the local registry (asynchronous).
1238−-- This function makes /no/ attempt to serialize and (in the case when the
1239−-- destination process resides on the same local node) therefore ensure that
1240−-- the payload is fully evaluated before it is delivered.
1241−unsafeNSend :: Serializable a => String -> a -> Process ()
1242−unsafeNSend = Unsafe.nsend
1243
835
1244-- | Named send to a process in a remote registry (asynchronous)
836-- | Named send to a process in a remote registry (asynchronous)
1245nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
837nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
1246−nsendRemote nid label msg = do
838+nsendRemote nid label msg =
1247− proc <- ask
1248− let us = processId proc
1249− let node = processNode proc
1250− if localNodeId node == nid
1251− then nsend label msg
1252− else let lbl = label ++ "@" ++ show nid in do
1253− liftIO $ traceEvent (localEventBus node)
1254− (MxSentToName lbl us (wrapMessage msg))
1255− sendCtrlMsg (Just nid) (NamedSend label (createMessage msg))
839+ sendCtrlMsg (Just nid) (NamedSend label (createMessage msg))
1256−
1257−-- | Named send to a process in a remote registry (asynchronous)
1258−-- This function makes /no/ attempt to serialize and (in the case when the
1259−-- destination process resides on the same local node) therefore ensure that
1260−-- the payload is fully evaluated before it is delivered.
1261−unsafeNSendRemote :: Serializable a => NodeId -> String -> a -> Process ()
1262−unsafeNSendRemote = Unsafe.nsendRemote
1263
840
1264--------------------------------------------------------------------------------
841--------------------------------------------------------------------------------
1265-- Closures --
842-- Closures --
@@ -1319,19 +896,17 @@ reconnectPort them = do
1319 liftIO $ disconnect node (ProcessIdentifier us) (SendPortIdentifier (sendPortId them))
896 liftIO $ disconnect node (ProcessIdentifier us) (SendPortIdentifier (sendPortId them))
1320
897
1321--------------------------------------------------------------------------------
898--------------------------------------------------------------------------------
1322−-- Auxiliary functions --
899+-- Debugging/Tracing --
1323--------------------------------------------------------------------------------
900--------------------------------------------------------------------------------
1324
901
1325−sendLocal :: (Serializable a) => ProcessId -> a -> Process ()
902+trace :: String -> Process ()
1326−sendLocal to msg =
903+trace s = do
1327− sendCtrlMsg Nothing $ LocalSend to (createUnencodedMessage msg)
904+ node <- processNode <$> ask
905+ liftIO $ Trace.trace (localTracer node) s
1328
906
1329−sendChanLocal :: (Serializable a) => SendPortId -> a -> Process ()
907+--------------------------------------------------------------------------------
1330−sendChanLocal spId msg =
908+-- Auxiliary functions --
1331− -- we *must* fully serialize/encode the message here, because
909+--------------------------------------------------------------------------------
1332− -- attempting to use `unsafeCoerce' in the node controller
1333− -- won't work since we know nothing about the required type
1334− sendCtrlMsg Nothing $ LocalPortSend spId (createUnencodedMessage msg)
1335
910
1336getMonitorRefFor :: Identifier -> Process MonitorRef
911getMonitorRefFor :: Identifier -> Process MonitorRef
1337getMonitorRefFor ident = do
912getMonitorRefFor ident = do
@@ -1361,3 +936,22 @@ monitor' ident = do
1361-- | Link to a process/node/channel
936-- | Link to a process/node/channel
1362link' :: Identifier -> Process ()
937link' :: Identifier -> Process ()
1363link' = sendCtrlMsg Nothing . Link
938link' = sendCtrlMsg Nothing . Link
939+
940+-- Send a control message
941+sendCtrlMsg :: Maybe NodeId -- ^ Nothing for the local node
942+ -> ProcessSignal -- ^ Message to send
943+ -> Process ()
944+sendCtrlMsg mNid signal = do
945+ proc <- ask
946+ let msg = NCMsg { ctrlMsgSender = ProcessIdentifier (processId proc)
947+ , ctrlMsgSignal = signal
948+ }
949+ case mNid of
950+ Nothing -> do
951+ liftIO $ writeChan (localCtrlChan (processNode proc)) msg
952+ Just nid ->
953+ liftIO $ sendBinary (processNode proc)
954+ (ProcessIdentifier (processId proc))
955+ (NodeIdentifier nid)
956+ WithImplicitReconnect
957+ msg
src/Control/Distributed/Process/Internal/StrictContainerAccessors.hs → distributed-process/src/Control/Distributed/Process/Internal/StrictContainerAccessors.hsrenamed

The content is the same.

src/Control/Distributed/Process/Internal/StrictList.hs → distributed-process/src/Control/Distributed/Process/Internal/StrictList.hsrenamed

The content is the same.

src/Control/Distributed/Process/Internal/StrictMVar.hs → distributed-process/src/Control/Distributed/Process/Internal/StrictMVar.hsrenamed+5−37
@@ -1,43 +1,33 @@
1-- | Like Control.Concurrent.MVar.Strict but reduce to HNF, not NF
1-- | Like Control.Concurrent.MVar.Strict but reduce to HNF, not NF
2−{-# LANGUAGE CPP, MagicHash, UnboxedTuples #-}
2+{-# LANGUAGE MagicHash, UnboxedTuples #-}
3module Control.Distributed.Process.Internal.StrictMVar
3module Control.Distributed.Process.Internal.StrictMVar
4 ( StrictMVar(StrictMVar)
4 ( StrictMVar(StrictMVar)
5 , newEmptyMVar
5 , newEmptyMVar
6 , newMVar
6 , newMVar
7 , takeMVar
7 , takeMVar
8 , putMVar
8 , putMVar
9− , readMVar
10 , withMVar
9 , withMVar
11 , modifyMVar_
10 , modifyMVar_
12 , modifyMVar
11 , modifyMVar
13− , modifyMVarMasked
14 , mkWeakMVar
12 , mkWeakMVar
15 ) where
13 ) where
16
14
17import Control.Applicative ((<$>))
15import Control.Applicative ((<$>))
18import Control.Monad ((>=>))
16import Control.Monad ((>=>))
19−#if MIN_VERSION_base(4,6,0)
20import Control.Exception (evaluate)
17import Control.Exception (evaluate)
21−#else
22−import Control.Exception (evaluate, mask_, onException)
23−#endif
24import qualified Control.Concurrent.MVar as MVar
18import qualified Control.Concurrent.MVar as MVar
25 ( MVar
19 ( MVar
26 , newEmptyMVar
20 , newEmptyMVar
27 , newMVar
21 , newMVar
28 , takeMVar
22 , takeMVar
29 , putMVar
23 , putMVar
30− , readMVar
31 , withMVar
24 , withMVar
32 , modifyMVar_
25 , modifyMVar_
33 , modifyMVar
26 , modifyMVar
34−#if MIN_VERSION_base(4,6,0)
35− , modifyMVarMasked
36−#endif
37 )
27 )
38import GHC.MVar (MVar(MVar))
28import GHC.MVar (MVar(MVar))
39−import GHC.IO (IO(IO), unIO)
29+import GHC.IO (IO(IO))
40−import GHC.Exts (mkWeak#)
30+import GHC.Prim (mkWeak#)
41import GHC.Weak (Weak(Weak))
31import GHC.Weak (Weak(Weak))
42
32
43newtype StrictMVar a = StrictMVar (MVar.MVar a)
33newtype StrictMVar a = StrictMVar (MVar.MVar a)
@@ -54,9 +44,6 @@ takeMVar (StrictMVar v) = MVar.takeMVar v
54putMVar :: StrictMVar a -> a -> IO ()
44putMVar :: StrictMVar a -> a -> IO ()
55putMVar (StrictMVar v) x = evaluate x >> MVar.putMVar v x
45putMVar (StrictMVar v) x = evaluate x >> MVar.putMVar v x
56
46
57−readMVar :: StrictMVar a -> IO a
58−readMVar (StrictMVar v) = MVar.readMVar v
59−
60withMVar :: StrictMVar a -> (a -> IO b) -> IO b
47withMVar :: StrictMVar a -> (a -> IO b) -> IO b
61withMVar (StrictMVar v) = MVar.withMVar v
48withMVar (StrictMVar v) = MVar.withMVar v
62
49
@@ -69,25 +56,6 @@ modifyMVar (StrictMVar v) f = MVar.modifyMVar v (f >=> evaluateFst)
69 evaluateFst :: (a, b) -> IO (a, b)
56 evaluateFst :: (a, b) -> IO (a, b)
70 evaluateFst (x, y) = evaluate x >> return (x, y)
57 evaluateFst (x, y) = evaluate x >> return (x, y)
71
58
72−modifyMVarMasked :: StrictMVar a -> (a -> IO (a, b)) -> IO b
73−modifyMVarMasked (StrictMVar v) f =
74−#if MIN_VERSION_base(4,6,0)
75− MVar.modifyMVarMasked v (f >=> evaluateFst)
76−#else
77− mask_ $ do
78− a <- MVar.takeMVar v
79− (a',b) <- (f a >>= evaluate) `onException` MVar.putMVar v a
80− MVar.putMVar v a'
81− return b
82−#endif
83− where
84− evaluateFst :: (a, b) -> IO (a, b)
85− evaluateFst (x, y) = evaluate x >> return (x, y)
86−
87mkWeakMVar :: StrictMVar a -> IO () -> IO (Weak (StrictMVar a))
59mkWeakMVar :: StrictMVar a -> IO () -> IO (Weak (StrictMVar a))
88−mkWeakMVar q@(StrictMVar (MVar m#)) f = IO $ \s ->
60+mkWeakMVar m@(StrictMVar (MVar m#)) f = IO $ \s ->
89−#if MIN_VERSION_base(4,9,0)
90− case mkWeak# m# q (unIO f) s of (# s', w #) -> (# s', Weak w #)
91−#else
92− case mkWeak# m# q f s of (# s', w #) -> (# s', Weak w #)
61+ case mkWeak# m# m f s of (# s1, w #) -> (# s1, Weak w #)
93−#endif
distributed-process/src/Control/Distributed/Process/Internal/Trace.hsnew file+82
@@ -0,0 +1,82 @@
1+-- | Simple (internal) system logging/tracing support.
2+module Control.Distributed.Process.Internal.Trace
3+ ( Tracer
4+ , trace
5+ , traceFormat
6+ , defaultTracer
7+ , logfileTracer
8+ , stopTracer
9+ ) where
10+
11+import Control.Concurrent (forkIO)
12+import Control.Concurrent.STM
13+ ( TQueue
14+ , newTQueueIO
15+ , readTQueue
16+ , writeTQueue
17+ , atomically
18+ )
19+import Control.Distributed.Process.Internal.Types
20+ ( forever'
21+ , Tracer(..)
22+ )
23+import Control.Exception
24+ ( catch
25+ , throwTo
26+ , SomeException
27+ , AsyncException(ThreadKilled)
28+ )
29+import Data.List (intersperse)
30+import Data.Time.Clock (getCurrentTime)
31+import Data.Time.Format (formatTime)
32+import Debug.Trace (traceEventIO)
33+
34+import Prelude hiding (catch)
35+
36+import System.Environment (getEnv)
37+import System.IO
38+ ( Handle
39+ , IOMode(AppendMode)
40+ , BufferMode(..)
41+ , openFile
42+ , hClose
43+ , hPutStrLn
44+ , hSetBuffering
45+ )
46+import System.Locale (defaultTimeLocale)
47+
48+defaultTracer :: IO Tracer
49+defaultTracer = do
50+ catch (getEnv "DISTRIBUTED_PROCESS_TRACE_FILE" >>= logfileTracer)
51+ (\(_ :: IOError) -> return (EventLogTracer traceEventIO))
52+
53+logfileTracer :: FilePath -> IO Tracer
54+logfileTracer p = do
55+ q <- newTQueueIO
56+ h <- openFile p AppendMode
57+ hSetBuffering h LineBuffering
58+ tid <- forkIO $ logger h q `catch` (\(_ :: SomeException) ->
59+ hClose h >> return ())
60+ return $ LogFileTracer tid q h
61+ where logger :: Handle -> TQueue String -> IO ()
62+ logger h q' = forever' $ do
63+ msg <- atomically $ readTQueue q'
64+ now <- getCurrentTime
65+ hPutStrLn h $ msg ++ (formatTime defaultTimeLocale " - %c" now)
66+
67+-- TODO: compatibility layer for GHC/base versions (e.g., where's killThread?)
68+
69+stopTracer :: Tracer -> IO () -- overzealous but harmless duplication of hClose
70+stopTracer (LogFileTracer tid _ h) = throwTo tid ThreadKilled >> hClose h
71+stopTracer _ = return ()
72+
73+trace :: Tracer -> String -> IO ()
74+trace (LogFileTracer _ q _) msg = atomically $ writeTQueue q msg
75+trace (EventLogTracer t) msg = t msg
76+
77+traceFormat :: Tracer
78+ -> String
79+ -> [String]
80+ -> IO ()
81+traceFormat t d ls = trace t $ concat (intersperse d ls)
82+
src/Control/Distributed/Process/Internal/Types.hs → distributed-process/src/Control/Distributed/Process/Internal/Types.hsrenamed+59−297
@@ -1,13 +1,3 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
2−{-# LANGUAGE ExistentialQuantification #-}
3−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4−{-# LANGUAGE GADTs #-}
5−{-# LANGUAGE DeriveGeneric #-}
6−{-# LANGUAGE CPP #-}
7−{-# LANGUAGE MultiParamTypeClasses #-}
8−{-# LANGUAGE TypeFamilies #-}
9−{-# LANGUAGE Rank2Types #-}
10−
11-- | Types used throughout the Cloud Haskell framework
1-- | Types used throughout the Cloud Haskell framework
12--
2--
13-- We collect all types used internally in a single module because
3-- We collect all types used internally in a single module because
@@ -24,14 +14,8 @@ module Control.Distributed.Process.Internal.Types
24 , nullProcessId
14 , nullProcessId
25 -- * Local nodes and processes
15 -- * Local nodes and processes
26 , LocalNode(..)
16 , LocalNode(..)
27− , LocalNodeState(..)
28− , ValidLocalNodeState(..)
29− , NodeClosedException(..)
30− , withValidLocalState
31− , modifyValidLocalState
32− , modifyValidLocalState_
33 , Tracer(..)
17 , Tracer(..)
34− , MxEventBus(..)
18+ , LocalNodeState(..)
35 , LocalProcess(..)
19 , LocalProcess(..)
36 , LocalProcessState(..)
20 , LocalProcessState(..)
37 , Process(..)
21 , Process(..)
@@ -45,10 +29,7 @@ module Control.Distributed.Process.Internal.Types
45 , ReceivePort(..)
29 , ReceivePort(..)
46 -- * Messages
30 -- * Messages
47 , Message(..)
31 , Message(..)
48− , isEncoded
49 , createMessage
32 , createMessage
50− , createUnencodedMessage
51− , unsafeCreateUnencodedMessage
52 , messageToPayload
33 , messageToPayload
53 , payloadToMessage
34 , payloadToMessage
54 -- * Node controller user-visible data types
35 -- * Node controller user-visible data types
@@ -72,7 +53,6 @@ module Control.Distributed.Process.Internal.Types
72 , RegisterReply(..)
53 , RegisterReply(..)
73 , ProcessInfo(..)
54 , ProcessInfo(..)
74 , ProcessInfoNone(..)
55 , ProcessInfoNone(..)
75− , NodeStats(..)
76 -- * Node controller internal data types
56 -- * Node controller internal data types
77 , NCMsg(..)
57 , NCMsg(..)
78 , ProcessSignal(..)
58 , ProcessSignal(..)
@@ -95,8 +75,7 @@ module Control.Distributed.Process.Internal.Types
95import System.Mem.Weak (Weak)
75import System.Mem.Weak (Weak)
96import Data.Map (Map)
76import Data.Map (Map)
97import Data.Int (Int32)
77import Data.Int (Int32)
98−import Data.Data (Data)
99−import Data.Typeable (Typeable, typeOf)
78+import Data.Typeable (Typeable)
100import Data.Binary (Binary(put, get), putWord8, getWord8, encode)
79import Data.Binary (Binary(put, get), putWord8, getWord8, encode)
101import qualified Data.ByteString as BSS (ByteString, concat, copy)
80import qualified Data.ByteString as BSS (ByteString, concat, copy)
102import qualified Data.ByteString.Lazy as BSL
81import qualified Data.ByteString.Lazy as BSL
@@ -104,23 +83,19 @@ import qualified Data.ByteString.Lazy as BSL
104 , toChunks
83 , toChunks
105 , splitAt
84 , splitAt
106 , fromChunks
85 , fromChunks
107− , length
108 )
86 )
109import qualified Data.ByteString.Lazy.Internal as BSL (ByteString(..))
87import qualified Data.ByteString.Lazy.Internal as BSL (ByteString(..))
110import Data.Accessor (Accessor, accessor)
88import Data.Accessor (Accessor, accessor)
111import Control.Category ((>>>))
89import Control.Category ((>>>))
112−import Control.DeepSeq (NFData(..))
113−import Control.Exception (Exception, throwIO)
90+import Control.Exception (Exception)
114import Control.Concurrent (ThreadId)
91import Control.Concurrent (ThreadId)
115import Control.Concurrent.Chan (Chan)
92import Control.Concurrent.Chan (Chan)
116import Control.Concurrent.STM (STM)
93import Control.Concurrent.STM (STM)
117−import Control.Concurrent.STM.TChan (TChan)
94+import qualified Control.Concurrent.STM as STM (TQueue)
118−import Control.Monad.Catch (MonadThrow(..), MonadCatch(..), MonadMask(..))
119import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)
95import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)
120−import Control.Applicative
96+import Control.Applicative (Applicative, Alternative, (<$>), (<*>))
121−import Control.Monad.Fix (MonadFix)
122import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
97import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
123−import Control.Monad.IO.Class (MonadIO(..))
98+import Control.Monad.IO.Class (MonadIO)
124import Control.Distributed.Process.Serializable
99import Control.Distributed.Process.Serializable
125 ( Fingerprint
100 ( Fingerprint
126 , Serializable
101 , Serializable
@@ -131,19 +106,11 @@ import Control.Distributed.Process.Serializable
131 , showFingerprint
106 , showFingerprint
132 )
107 )
133import Control.Distributed.Process.Internal.CQueue (CQueue)
108import Control.Distributed.Process.Internal.CQueue (CQueue)
134−import Control.Distributed.Process.Internal.StrictMVar
109+import Control.Distributed.Process.Internal.StrictMVar (StrictMVar)
135− ( StrictMVar
136− , withMVar
137− , modifyMVar
138− , modifyMVar_
139− )
140import Control.Distributed.Process.Internal.WeakTQueue (TQueue)
110import Control.Distributed.Process.Internal.WeakTQueue (TQueue)
141import Control.Distributed.Static (RemoteTable, Closure)
111import Control.Distributed.Static (RemoteTable, Closure)
142import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC (mapMaybe)
112import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC (mapMaybe)
143−
113+import System.IO (Handle)
144−import Data.Hashable
145−import GHC.Generics
146−import Prelude
147
114
148--------------------------------------------------------------------------------
115--------------------------------------------------------------------------------
149-- Node and process identifiers --
116-- Node and process identifiers --
@@ -151,10 +118,8 @@ import Prelude
151
118
152-- | Node identifier
119-- | Node identifier
153newtype NodeId = NodeId { nodeAddress :: NT.EndPointAddress }
120newtype NodeId = NodeId { nodeAddress :: NT.EndPointAddress }
154− deriving (Eq, Ord, Typeable, Data, Generic)
121+ deriving (Eq, Ord, Binary, Typeable)
155−instance Binary NodeId where
122+
156−instance NFData NodeId where rnf (NodeId a) = rnf a `seq` ()
157−instance Hashable NodeId where
158instance Show NodeId where
123instance Show NodeId where
159 show (NodeId addr) = "nid://" ++ show addr
124 show (NodeId addr) = "nid://" ++ show addr
160
125
@@ -164,9 +129,7 @@ data LocalProcessId = LocalProcessId
164 { lpidUnique :: {-# UNPACK #-} !Int32
129 { lpidUnique :: {-# UNPACK #-} !Int32
165 , lpidCounter :: {-# UNPACK #-} !Int32
130 , lpidCounter :: {-# UNPACK #-} !Int32
166 }
131 }
167− deriving (Eq, Ord, Typeable, Data, Generic, Show)
132+ deriving (Eq, Ord, Typeable, Show)
168−
169−instance Hashable LocalProcessId where
170
133
171-- | Process identifier
134-- | Process identifier
172data ProcessId = ProcessId
135data ProcessId = ProcessId
@@ -175,11 +138,7 @@ data ProcessId = ProcessId
175 -- | Node-local identifier for the process
138 -- | Node-local identifier for the process
176 , processLocalId :: {-# UNPACK #-} !LocalProcessId
139 , processLocalId :: {-# UNPACK #-} !LocalProcessId
177 }
140 }
178− deriving (Eq, Ord, Typeable, Data, Generic)
141+ deriving (Eq, Ord, Typeable)
179−
180−instance Binary ProcessId where
181−instance NFData ProcessId where rnf (ProcessId n _) = rnf n `seq` ()
182−instance Hashable ProcessId where
183
142
184instance Show ProcessId where
143instance Show ProcessId where
185 show (ProcessId (NodeId addr) (LocalProcessId _ lid))
144 show (ProcessId (NodeId addr) (LocalProcessId _ lid))
@@ -190,13 +149,7 @@ data Identifier =
190 NodeIdentifier !NodeId
149 NodeIdentifier !NodeId
191 | ProcessIdentifier !ProcessId
150 | ProcessIdentifier !ProcessId
192 | SendPortIdentifier !SendPortId
151 | SendPortIdentifier !SendPortId
193− deriving (Eq, Ord, Generic)
152+ deriving (Eq, Ord)
194−
195−instance Hashable Identifier where
196−instance NFData Identifier where
197− rnf (NodeIdentifier n) = rnf n `seq` ()
198− rnf (ProcessIdentifier n) = rnf n `seq` ()
199− rnf n@SendPortIdentifier{} = n `seq` ()
200
153
201instance Show Identifier where
154instance Show Identifier where
202 show (NodeIdentifier nid) = show nid
155 show (NodeIdentifier nid) = show nid
@@ -227,30 +180,10 @@ nullProcessId nid =
227-- Local nodes and processes --
180-- Local nodes and processes --
228--------------------------------------------------------------------------------
181--------------------------------------------------------------------------------
229
182
230−-- | Provides access to the trace controller
183+-- | Required for system tracing in the node controller
231−data Tracer = Tracer
184+data Tracer =
232− {
185+ LogFileTracer !ThreadId !(STM.TQueue String) !Handle
233− -- | Process id for the currently active trace handler
186+ | EventLogTracer !(String -> IO ())
234− tracerPid :: !ProcessId
235− -- | Weak reference to the tracer controller's mailbox
236− , weakQ :: !(Weak (CQueue Message))
237− }
238−
239−-- | Local system management event bus state
240−data MxEventBus =
241− MxEventBusInitialising
242− | MxEventBus
243− {
244− -- | Process id of the management agent controller process
245− agent :: !ProcessId
246− -- | Configuration for the local trace controller
247− , tracer :: !Tracer
248− -- | Weak reference to the management agent controller's mailbox
249− , evbuss :: !(Weak (CQueue Message))
250− -- | API for adding management agents to a running node
251− , mxNew :: !(((TChan Message, TChan Message) -> Process ()) -> IO ProcessId)
252−-- , mxReg :: !(StrictMVar (Map MxAgentId ))
253− }
254
187
255-- | Local nodes
188-- | Local nodes
256data LocalNode = LocalNode
189data LocalNode = LocalNode
@@ -262,8 +195,8 @@ data LocalNode = LocalNode
262 , localState :: !(StrictMVar LocalNodeState)
195 , localState :: !(StrictMVar LocalNodeState)
263 -- | Channel for the node controller
196 -- | Channel for the node controller
264 , localCtrlChan :: !(Chan NCMsg)
197 , localCtrlChan :: !(Chan NCMsg)
265− -- | Internal management event bus
198+ -- | Current active system debug/trace log
266− , localEventBus :: !MxEventBus
199+ , localTracer :: !Tracer
267 -- | Runtime lookup table for supporting closures
200 -- | Runtime lookup table for supporting closures
268 -- TODO: this should be part of the CH state, not the local endpoint state
201 -- TODO: this should be part of the CH state, not the local endpoint state
269 , remoteTable :: !RemoteTable
202 , remoteTable :: !RemoteTable
@@ -273,11 +206,7 @@ data ImplicitReconnect = WithImplicitReconnect | NoImplicitReconnect
273 deriving (Eq, Show)
206 deriving (Eq, Show)
274
207
275-- | Local node state
208-- | Local node state
276−data LocalNodeState =
209+data LocalNodeState = LocalNodeState
277− LocalNodeValid {-# UNPACK #-} !ValidLocalNodeState
278− | LocalNodeClosed
279−
280−data ValidLocalNodeState = ValidLocalNodeState
281 { -- | Processes running on this node
210 { -- | Processes running on this node
282 _localProcesses :: !(Map LocalProcessId LocalProcess)
211 _localProcesses :: !(Map LocalProcessId LocalProcess)
283 -- | Counter to assign PIDs
212 -- | Counter to assign PIDs
@@ -290,40 +219,6 @@ data ValidLocalNodeState = ValidLocalNodeState
290 (NT.Connection, ImplicitReconnect))
219 (NT.Connection, ImplicitReconnect))
291 }
220 }
292
221
293−-- | Thrown by some primitives when they notice the node has been closed.
294−data NodeClosedException = NodeClosedException NodeId
295− deriving (Show, Typeable)
296−
297−instance Exception NodeClosedException
298−
299−-- | Wrapper around 'withMVar' that checks that the local node is still in
300−-- a valid state.
301−withValidLocalState :: LocalNode
302− -> (ValidLocalNodeState -> IO r)
303− -> IO r
304−withValidLocalState node f = withMVar (localState node) $ \st -> case st of
305− LocalNodeValid vst -> f vst
306− LocalNodeClosed -> throwIO $ NodeClosedException (localNodeId node)
307−
308−-- | Wrapper around 'modifyMVar' that checks that the local node is still in
309−-- a valid state.
310−modifyValidLocalState :: LocalNode
311− -> (ValidLocalNodeState -> IO (ValidLocalNodeState, a))
312− -> IO (Maybe a)
313−modifyValidLocalState node f = modifyMVar (localState node) $ \st -> case st of
314− LocalNodeValid vst -> do (vst', a) <- f vst
315− return (LocalNodeValid vst', Just a)
316− LocalNodeClosed -> return (LocalNodeClosed, Nothing)
317−
318−-- | Wrapper around 'modifyMVar_' that checks that the local node is still in
319−-- a valid state.
320−modifyValidLocalState_ :: LocalNode
321− -> (ValidLocalNodeState -> IO ValidLocalNodeState)
322− -> IO ()
323−modifyValidLocalState_ node f = modifyMVar_ (localState node) $ \st -> case st of
324− LocalNodeValid vst -> LocalNodeValid <$> f vst
325− LocalNodeClosed -> return LocalNodeClosed
326−
327-- | Processes running on our local node
222-- | Processes running on our local node
328data LocalProcess = LocalProcess
223data LocalProcess = LocalProcess
329 { processQueue :: !(CQueue Message)
224 { processQueue :: !(CQueue Message)
@@ -350,50 +245,7 @@ data LocalProcessState = LocalProcessState
350newtype Process a = Process {
245newtype Process a = Process {
351 unProcess :: ReaderT LocalProcess IO a
246 unProcess :: ReaderT LocalProcess IO a
352 }
247 }
353− deriving ( Applicative
354− , Functor
355− , Monad
356− , MonadFix
357− , MonadIO
358− , MonadReader LocalProcess
248+ deriving (Functor, Monad, MonadIO, MonadReader LocalProcess, Typeable, Applicative)
359− , Typeable
360− )
361−
362−instance MonadThrow Process where
363− throwM = liftIO . throwIO
364−instance MonadCatch Process where
365− catch p h = do
366− lproc <- ask
367− liftIO $ catch (runLocalProcess lproc p) (runLocalProcess lproc . h)
368−instance MonadMask Process where
369−#if MIN_VERSION_exceptions(0,10,0)
370− generalBracket acquire release inner = do
371− lproc <- ask
372− liftIO $
373− generalBracket (runLocalProcess lproc acquire)
374− (\a e -> runLocalProcess lproc $ release a e)
375− (runLocalProcess lproc . inner)
376−#endif
377− mask p = do
378− lproc <- ask
379− liftIO $ mask $ \restore ->
380− runLocalProcess lproc (p (liftRestore restore))
381− where
382− liftRestore :: (forall a. IO a -> IO a)
383− -> (forall a. Process a -> Process a)
384− liftRestore restoreIO = \p2 -> do
385− ourLocalProc <- ask
386− liftIO $ restoreIO $ runLocalProcess ourLocalProc p2
387− uninterruptibleMask p = do
388− lproc <- ask
389− liftIO $ uninterruptibleMask $ \restore ->
390− runLocalProcess lproc (p (liftRestore restore))
391− where
392− liftRestore :: (forall a. IO a -> IO a)
393− -> (forall a. Process a -> Process a)
394− liftRestore restoreIO = \p2 -> do
395− ourLocalProc <- ask
396− liftIO $ restoreIO $ runLocalProcess ourLocalProc p2
397
249
398--------------------------------------------------------------------------------
250--------------------------------------------------------------------------------
399-- Typed channels --
251-- Typed channels --
@@ -411,16 +263,12 @@ data SendPortId = SendPortId {
411 -- | Process-local ID of the channel
263 -- | Process-local ID of the channel
412 , sendPortLocalId :: {-# UNPACK #-} !LocalSendPortId
264 , sendPortLocalId :: {-# UNPACK #-} !LocalSendPortId
413 }
265 }
414− deriving (Eq, Ord, Typeable, Generic)
266+ deriving (Eq, Ord)
415
267
416−instance Hashable SendPortId where
417instance Show SendPortId where
268instance Show SendPortId where
418 show (SendPortId (ProcessId (NodeId addr) (LocalProcessId _ plid)) clid)
269 show (SendPortId (ProcessId (NodeId addr) (LocalProcessId _ plid)) clid)
419 = "cid://" ++ show addr ++ ":" ++ show plid ++ ":" ++ show clid
270 = "cid://" ++ show addr ++ ":" ++ show plid ++ ":" ++ show clid
420
271
421−instance NFData SendPortId where
422− rnf (SendPortId p _) = rnf p `seq` ()
423−
424data TypedChannel = forall a. Serializable a => TypedChannel (Weak (TQueue a))
272data TypedChannel = forall a. Serializable a => TypedChannel (Weak (TQueue a))
425
273
426-- | The send send of a typed channel (serializable)
274-- | The send send of a typed channel (serializable)
@@ -428,11 +276,7 @@ newtype SendPort a = SendPort {
428 -- | The (unique) ID of this send port
276 -- | The (unique) ID of this send port
429 sendPortId :: SendPortId
277 sendPortId :: SendPortId
430 }
278 }
431− deriving (Typeable, Generic, Show, Eq, Ord)
279+ deriving (Typeable, Binary, Show, Eq, Ord)
432−
433−instance (Serializable a) => Binary (SendPort a) where
434−instance (Hashable a) => Hashable (SendPort a) where
435−instance (NFData a) => NFData (SendPort a) where rnf (SendPort x) = x `seq` ()
436
280
437-- | The receive end of a typed channel (not serializable)
281-- | The receive end of a typed channel (not serializable)
438--
282--
@@ -457,66 +301,25 @@ data ReceivePort a =
457--------------------------------------------------------------------------------
301--------------------------------------------------------------------------------
458
302
459-- | Messages consist of their typeRep fingerprint and their encoding
303-- | Messages consist of their typeRep fingerprint and their encoding
460−data Message =
304+data Message = Message
461− EncodedMessage
462 { messageFingerprint :: !Fingerprint
305 { messageFingerprint :: !Fingerprint
463 , messageEncoding :: !BSL.ByteString
306 , messageEncoding :: !BSL.ByteString
464− } |
465− forall a . Serializable a =>
466− UnencodedMessage
467− {
468− messageFingerprint :: !Fingerprint
469− , messagePayload :: !a
470 }
307 }
471− deriving (Typeable)
472−
473−instance NFData Message where
474−#if MIN_VERSION_bytestring(0,10,0)
475− rnf (EncodedMessage _ e) = rnf e `seq` ()
476−#else
477− rnf (EncodedMessage _ e) = BSL.length e `seq` ()
478−#endif
479− rnf (UnencodedMessage _ a) = e `seq` ()
480− where e = BSL.length (encode a)
481
308
482instance Show Message where
309instance Show Message where
483− show (EncodedMessage fp enc) = show enc ++ " :: " ++ showFingerprint fp []
310+ show (Message fp enc) = show enc ++ " :: " ++ showFingerprint fp []
484− show (UnencodedMessage _ uenc) = "[unencoded message] :: " ++ (show $ typeOf uenc)
485−
486−-- | /internal use only/.
487−isEncoded :: Message -> Bool
488−isEncoded (EncodedMessage _ _) = True
489−isEncoded _ = False
490−-- [note] isEncoded:
491−-- This is just as internal as it looks and yes, it does feel a bit odd that
492−-- we're exporting it for use, however DPP does a /lot/ of work with low
493−-- level APIs such as unwrapMessage and handleMessage, and in the process
494−-- tries very hard to avoid copying (and re-serialisation) where possible.
495−-- Being able to determine that a message is encoded (or otherwise) makes
496−-- that a lot more manageable.
497
311
498-- | Turn any serialiable term into a message
312-- | Turn any serialiable term into a message
499createMessage :: Serializable a => a -> Message
313createMessage :: Serializable a => a -> Message
500−createMessage a = EncodedMessage (fingerprint a) (encode a)
314+createMessage a = Message (fingerprint a) (encode a)
501−
502−-- | Turn any serializable term into an unencoded/local message
503−createUnencodedMessage :: Serializable a => a -> Message
504−createUnencodedMessage a =
505− let encoded = encode a in BSL.length encoded `seq` UnencodedMessage (fingerprint a) a
506−
507−-- | Turn any serializable term into an unencodede/local message, without
508−-- evalutaing it! This is a dangerous business.
509−unsafeCreateUnencodedMessage :: Serializable a => a -> Message
510−unsafeCreateUnencodedMessage a = UnencodedMessage (fingerprint a) a
511
315
512-- | Serialize a message
316-- | Serialize a message
513messageToPayload :: Message -> [BSS.ByteString]
317messageToPayload :: Message -> [BSS.ByteString]
514−messageToPayload (EncodedMessage fp enc) = encodeFingerprint fp : BSL.toChunks enc
318+messageToPayload (Message fp enc) = encodeFingerprint fp : BSL.toChunks enc
515−messageToPayload (UnencodedMessage fp m) = messageToPayload ((EncodedMessage fp (encode m)))
516
319
517-- | Deserialize a message
320-- | Deserialize a message
518payloadToMessage :: [BSS.ByteString] -> Message
321payloadToMessage :: [BSS.ByteString] -> Message
519−payloadToMessage payload = EncodedMessage fp (copy msg)
322+payloadToMessage payload = Message fp (copy msg)
520 where
323 where
521 encFp :: BSL.ByteString
324 encFp :: BSL.ByteString
522 msg :: BSL.ByteString
325 msg :: BSL.ByteString
@@ -541,11 +344,7 @@ data MonitorRef = MonitorRef
541 -- | Unique to distinguish multiple monitor requests by the same process
344 -- | Unique to distinguish multiple monitor requests by the same process
542 , monitorRefCounter :: !Int32
345 , monitorRefCounter :: !Int32
543 }
346 }
544− deriving (Eq, Ord, Show, Typeable, Generic)
347+ deriving (Eq, Ord, Show)
545−instance Hashable MonitorRef
546−
547−instance NFData MonitorRef where
548− rnf (MonitorRef i _) = rnf i `seq` ()
549
348
550-- | Message sent by process monitors
349-- | Message sent by process monitors
551data ProcessMonitorNotification =
350data ProcessMonitorNotification =
@@ -579,11 +378,9 @@ data PortLinkException =
579
378
580-- | Exception thrown when a process attempts to register
379-- | Exception thrown when a process attempts to register
581-- a process under an already-registered name or to
380-- a process under an already-registered name or to
582−-- unregister a name that hasn't been registered. Returns
381+-- unregister a name that hasn't been registered
583−-- the name and the identifier of the process that owns it,
584−-- if any.
585data ProcessRegistrationException =
382data ProcessRegistrationException =
586− ProcessRegistrationException !String !(Maybe ProcessId)
383+ ProcessRegistrationException !String
587 deriving (Typeable, Show)
384 deriving (Typeable, Show)
588
385
589-- | Internal exception thrown indirectly by 'exit'
386-- | Internal exception thrown indirectly by 'exit'
@@ -615,10 +412,6 @@ data DiedReason =
615 | DiedUnknownId
412 | DiedUnknownId
616 deriving (Show, Eq)
413 deriving (Show, Eq)
617
414
618−instance NFData DiedReason where
619− rnf (DiedException s) = rnf s `seq` ()
620− rnf x = x `seq` ()
621−
622-- | (Asynchronous) reply from unmonitor
415-- | (Asynchronous) reply from unmonitor
623newtype DidUnmonitor = DidUnmonitor MonitorRef
416newtype DidUnmonitor = DidUnmonitor MonitorRef
624 deriving (Typeable, Binary)
417 deriving (Typeable, Binary)
@@ -637,7 +430,7 @@ newtype DidUnlinkPort = DidUnlinkPort SendPortId
637
430
638-- | 'SpawnRef' are used to return pids of spawned processes
431-- | 'SpawnRef' are used to return pids of spawned processes
639newtype SpawnRef = SpawnRef Int32
432newtype SpawnRef = SpawnRef Int32
640− deriving (Show, Binary, Typeable, Eq, Ord)
433+ deriving (Show, Binary, Typeable, Eq)
641
434
642-- | (Asynchronius) reply from 'spawn'
435-- | (Asynchronius) reply from 'spawn'
643data DidSpawn = DidSpawn SpawnRef ProcessId
436data DidSpawn = DidSpawn SpawnRef ProcessId
@@ -648,23 +441,14 @@ data WhereIsReply = WhereIsReply String (Maybe ProcessId)
648 deriving (Show, Typeable)
441 deriving (Show, Typeable)
649
442
650-- | (Asynchronous) reply from 'register' and 'unregister'
443-- | (Asynchronous) reply from 'register' and 'unregister'
651−data RegisterReply = RegisterReply String Bool (Maybe ProcessId)
444+data RegisterReply = RegisterReply String Bool
652 deriving (Show, Typeable)
445 deriving (Show, Typeable)
653
446
654−data NodeStats = NodeStats {
655− nodeStatsNode :: NodeId
656− , nodeStatsRegisteredNames :: Int
657− , nodeStatsMonitors :: Int
658− , nodeStatsLinks :: Int
659− , nodeStatsProcesses :: Int
660− }
661− deriving (Show, Eq, Typeable)
662−
663-- | Provide information about a running process
447-- | Provide information about a running process
664data ProcessInfo = ProcessInfo {
448data ProcessInfo = ProcessInfo {
665 infoNode :: NodeId
449 infoNode :: NodeId
666 , infoRegisteredNames :: [String]
450 , infoRegisteredNames :: [String]
667− , infoMessageQueueLength :: Int
451+ , infoMessageQueueLength :: Maybe Int
668 , infoMonitors :: [(ProcessId, MonitorRef)]
452 , infoMonitors :: [(ProcessId, MonitorRef)]
669 , infoLinks :: [ProcessId]
453 , infoLinks :: [ProcessId]
670 } deriving (Show, Eq, Typeable)
454 } deriving (Show, Eq, Typeable)
@@ -694,28 +478,23 @@ data ProcessSignal =
694 | WhereIs !String
478 | WhereIs !String
695 | Register !String !NodeId !(Maybe ProcessId) !Bool -- Use 'Nothing' to unregister, use True to force reregister
479 | Register !String !NodeId !(Maybe ProcessId) !Bool -- Use 'Nothing' to unregister, use True to force reregister
696 | NamedSend !String !Message
480 | NamedSend !String !Message
697− | UnreliableSend !LocalProcessId !Message
698− | LocalSend !ProcessId !Message
699− | LocalPortSend !SendPortId !Message
700 | Kill !ProcessId !String
481 | Kill !ProcessId !String
701 | Exit !ProcessId !Message
482 | Exit !ProcessId !Message
702 | GetInfo !ProcessId
483 | GetInfo !ProcessId
703− | SigShutdown
704− | GetNodeStats !NodeId
705 deriving Show
484 deriving Show
706
485
707--------------------------------------------------------------------------------
486--------------------------------------------------------------------------------
708-- Binary instances --
487-- Binary instances --
709--------------------------------------------------------------------------------
488--------------------------------------------------------------------------------
710
489
711−instance Binary Message where
712− put msg = put $ messageToPayload msg
713− get = payloadToMessage <$> get
714−
715instance Binary LocalProcessId where
490instance Binary LocalProcessId where
716 put lpid = put (lpidUnique lpid) >> put (lpidCounter lpid)
491 put lpid = put (lpidUnique lpid) >> put (lpidCounter lpid)
717 get = LocalProcessId <$> get <*> get
492 get = LocalProcessId <$> get <*> get
718
493
494+instance Binary ProcessId where
495+ put pid = put (processNodeId pid) >> put (processLocalId pid)
496+ get = ProcessId <$> get <*> get
497+
719instance Binary ProcessMonitorNotification where
498instance Binary ProcessMonitorNotification where
720 put (ProcessMonitorNotification ref pid reason) = put ref >> put pid >> put reason
499 put (ProcessMonitorNotification ref pid reason) = put ref >> put pid >> put reason
721 get = ProcessMonitorNotification <$> get <*> get <*> get
500 get = ProcessMonitorNotification <$> get <*> get <*> get
@@ -737,23 +516,18 @@ instance Binary MonitorRef where
737 get = MonitorRef <$> get <*> get
516 get = MonitorRef <$> get <*> get
738
517
739instance Binary ProcessSignal where
518instance Binary ProcessSignal where
740− put (Link pid) = putWord8 0 >> put pid
519+ put (Link pid) = putWord8 0 >> put pid
741− put (Unlink pid) = putWord8 1 >> put pid
520+ put (Unlink pid) = putWord8 1 >> put pid
742− put (Monitor ref) = putWord8 2 >> put ref
521+ put (Monitor ref) = putWord8 2 >> put ref
743− put (Unmonitor ref) = putWord8 3 >> put ref
522+ put (Unmonitor ref) = putWord8 3 >> put ref
744− put (Died who reason) = putWord8 4 >> put who >> put reason
523+ put (Died who reason) = putWord8 4 >> put who >> put reason
745− put (Spawn proc ref) = putWord8 5 >> put proc >> put ref
524+ put (Spawn proc ref) = putWord8 5 >> put proc >> put ref
746− put (WhereIs label) = putWord8 6 >> put label
525+ put (WhereIs label) = putWord8 6 >> put label
747 put (Register label nid pid force) = putWord8 7 >> put label >> put nid >> put pid >> put force
526 put (Register label nid pid force) = putWord8 7 >> put label >> put nid >> put pid >> put force
748− put (NamedSend label msg) = putWord8 8 >> put label >> put (messageToPayload msg)
527+ put (NamedSend label msg) = putWord8 8 >> put label >> put (messageToPayload msg)
749− put (Kill pid reason) = putWord8 9 >> put pid >> put reason
528+ put (Kill pid reason) = putWord8 9 >> put pid >> put reason
750− put (Exit pid reason) = putWord8 10 >> put pid >> put (messageToPayload reason)
529+ put (Exit pid reason) = putWord8 10 >> put pid >> put (messageToPayload reason)
751− put (LocalSend to' msg) = putWord8 11 >> put to' >> put (messageToPayload msg)
752− put (LocalPortSend sid msg) = putWord8 12 >> put sid >> put (messageToPayload msg)
753− put (UnreliableSend lpid msg) = putWord8 13 >> put lpid >> put (messageToPayload msg)
754− put (GetInfo about) = putWord8 30 >> put about
530+ put (GetInfo about) = putWord8 30 >> put about
755− put (SigShutdown) = putWord8 31
756− put (GetNodeStats nid) = putWord8 32 >> put nid
757 get = do
531 get = do
758 header <- getWord8
532 header <- getWord8
759 case header of
533 case header of
@@ -768,12 +542,7 @@ instance Binary ProcessSignal where
768 8 -> NamedSend <$> get <*> (payloadToMessage <$> get)
542 8 -> NamedSend <$> get <*> (payloadToMessage <$> get)
769 9 -> Kill <$> get <*> get
543 9 -> Kill <$> get <*> get
770 10 -> Exit <$> get <*> (payloadToMessage <$> get)
544 10 -> Exit <$> get <*> (payloadToMessage <$> get)
771− 11 -> LocalSend <$> get <*> (payloadToMessage <$> get)
772− 12 -> LocalPortSend <$> get <*> (payloadToMessage <$> get)
773− 13 -> UnreliableSend <$> get <*> (payloadToMessage <$> get)
774 30 -> GetInfo <$> get
545 30 -> GetInfo <$> get
775− 31 -> return SigShutdown
776− 32 -> GetNodeStats <$> get
777 _ -> fail "ProcessSignal.get: invalid"
546 _ -> fail "ProcessSignal.get: invalid"
778
547
779instance Binary DiedReason where
548instance Binary DiedReason where
@@ -817,8 +586,8 @@ instance Binary WhereIsReply where
817 get = WhereIsReply <$> get <*> get
586 get = WhereIsReply <$> get <*> get
818
587
819instance Binary RegisterReply where
588instance Binary RegisterReply where
820− put (RegisterReply label ok owner) = put label >> put ok >> put owner
589+ put (RegisterReply label ok) = put label >> put ok
821− get = RegisterReply <$> get <*> get <*> get
590+ get = RegisterReply <$> get <*> get
822
591
823instance Binary ProcessInfo where
592instance Binary ProcessInfo where
824 get = ProcessInfo <$> get <*> get <*> get <*> get <*> get
593 get = ProcessInfo <$> get <*> get <*> get <*> get <*> get
@@ -828,14 +597,6 @@ instance Binary ProcessInfo where
828 >> put (infoMonitors pInfo)
597 >> put (infoMonitors pInfo)
829 >> put (infoLinks pInfo)
598 >> put (infoLinks pInfo)
830
599
831−instance Binary NodeStats where
832− get = NodeStats <$> get <*> get <*> get <*> get <*> get
833− put nStats = put (nodeStatsNode nStats)
834− >> put (nodeStatsRegisteredNames nStats)
835− >> put (nodeStatsMonitors nStats)
836− >> put (nodeStatsLinks nStats)
837− >> put (nodeStatsProcesses nStats)
838−
839instance Binary ProcessInfoNone where
600instance Binary ProcessInfoNone where
840 get = ProcessInfoNone <$> get
601 get = ProcessInfoNone <$> get
841 put (ProcessInfoNone r) = put r
602 put (ProcessInfoNone r) = put r
@@ -844,23 +605,23 @@ instance Binary ProcessInfoNone where
844-- Accessors --
605-- Accessors --
845--------------------------------------------------------------------------------
606--------------------------------------------------------------------------------
846
607
847−localProcesses :: Accessor ValidLocalNodeState (Map LocalProcessId LocalProcess)
608+localProcesses :: Accessor LocalNodeState (Map LocalProcessId LocalProcess)
848localProcesses = accessor _localProcesses (\procs st -> st { _localProcesses = procs })
609localProcesses = accessor _localProcesses (\procs st -> st { _localProcesses = procs })
849
610
850−localPidCounter :: Accessor ValidLocalNodeState Int32
611+localPidCounter :: Accessor LocalNodeState Int32
851localPidCounter = accessor _localPidCounter (\ctr st -> st { _localPidCounter = ctr })
612localPidCounter = accessor _localPidCounter (\ctr st -> st { _localPidCounter = ctr })
852
613
853−localPidUnique :: Accessor ValidLocalNodeState Int32
614+localPidUnique :: Accessor LocalNodeState Int32
854localPidUnique = accessor _localPidUnique (\unq st -> st { _localPidUnique = unq })
615localPidUnique = accessor _localPidUnique (\unq st -> st { _localPidUnique = unq })
855
616
856−localConnections :: Accessor ValidLocalNodeState (Map (Identifier, Identifier) (NT.Connection, ImplicitReconnect))
617+localConnections :: Accessor LocalNodeState (Map (Identifier, Identifier) (NT.Connection, ImplicitReconnect))
857localConnections = accessor _localConnections (\conns st -> st { _localConnections = conns })
618localConnections = accessor _localConnections (\conns st -> st { _localConnections = conns })
858
619
859−localProcessWithId :: LocalProcessId -> Accessor ValidLocalNodeState (Maybe LocalProcess)
620+localProcessWithId :: LocalProcessId -> Accessor LocalNodeState (Maybe LocalProcess)
860localProcessWithId lpid = localProcesses >>> DAC.mapMaybe lpid
621localProcessWithId lpid = localProcesses >>> DAC.mapMaybe lpid
861
622
862−localConnectionBetween :: Identifier -> Identifier -> Accessor ValidLocalNodeState (Maybe (NT.Connection, ImplicitReconnect))
623+localConnectionBetween :: Identifier -> Identifier -> Accessor LocalNodeState (Maybe (NT.Connection, ImplicitReconnect))
863−localConnectionBetween from' to' = localConnections >>> DAC.mapMaybe (from', to')
624+localConnectionBetween from to = localConnections >>> DAC.mapMaybe (from, to)
864
625
865monitorCounter :: Accessor LocalProcessState Int32
626monitorCounter :: Accessor LocalProcessState Int32
866monitorCounter = accessor _monitorCounter (\cnt st -> st { _monitorCounter = cnt })
627monitorCounter = accessor _monitorCounter (\cnt st -> st { _monitorCounter = cnt })
@@ -885,3 +646,4 @@ typedChannelWithId cid = typedChannels >>> DAC.mapMaybe cid
885{-# INLINE forever' #-}
646{-# INLINE forever' #-}
886forever' :: Monad m => m a -> m b
647forever' :: Monad m => m a -> m b
887forever' a = let a' = a >> a' in a'
648forever' a = let a' = a >> a' in a'
649+
src/Control/Distributed/Process/Internal/WeakTQueue.hs → distributed-process/src/Control/Distributed/Process/Internal/WeakTQueue.hsrenamed+3−9
@@ -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 #)
105−#else
106 case mkWeak# write# q f s of (# s', w #) -> (# s', Weak w #)
101 case mkWeak# write# q f s of (# s', w #) -> (# s', Weak w #)
107−#endif
src/Control/Distributed/Process/Node.hs → distributed-process/src/Control/Distributed/Process/Node.hsrenamed+269−557
@@ -1,14 +1,7 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DeriveDataTypeable #-}
3−{-# LANGUAGE ScopedTypeVariables #-}
4−{-# LANGUAGE RankNTypes #-}
5−{-# LANGUAGE BangPatterns #-}
6−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7−{-# LANGUAGE RecordWildCards #-}
8−{-# LANGUAGE MagicHash #-}
9−
10-- | Local nodes
1-- | Local nodes
11--
2--
3+-- TODO: Calls to 'sendBinary' and co by the node controller may stall the
4+-- node controller.
12module Control.Distributed.Process.Node
5module Control.Distributed.Process.Node
13 ( LocalNode
6 ( LocalNode
14 , newLocalNode
7 , newLocalNode
@@ -19,7 +12,9 @@ module Control.Distributed.Process.Node
19 , localNodeId
12 , localNodeId
20 ) where
13 ) where
21
14
22−-- TODO: Calls to 'sendBinary' and co (by the NC) may stall the node controller.
15+#if ! MIN_VERSION_base(4,6,0)
16+import Prelude hiding (catch)
17+#endif
23
18
24import System.IO (fixIO, hPutStrLn, stderr)
19import System.IO (fixIO, hPutStrLn, stderr)
25import System.Mem.Weak (Weak, deRefWeak)
20import System.Mem.Weak (Weak, deRefWeak)
@@ -28,73 +23,45 @@ import Data.Binary (decode)
28import Data.Map (Map)
23import Data.Map (Map)
29import qualified Data.Map as Map
24import qualified Data.Map as Map
30 ( empty
25 ( empty
31− , delete
32 , toList
26 , toList
33 , fromList
27 , fromList
34− , insert
28+ , filter
35− , lookup
36− , partition
37 , partitionWithKey
29 , partitionWithKey
38 , elems
30 , elems
39− , size
40 , filterWithKey
31 , filterWithKey
41 , foldlWithKey
32 , foldlWithKey
42 )
33 )
43−import Data.Time.Format (formatTime)
44−#if MIN_VERSION_time(1,5,0)
45−import Data.Time.Format (defaultTimeLocale)
46−#else
47−import System.Locale (defaultTimeLocale)
48−#endif
49import Data.Set (Set)
34import Data.Set (Set)
50import qualified Data.Set as Set
35import qualified Data.Set as Set
51 ( empty
36 ( empty
52 , insert
37 , insert
53 , delete
38 , delete
54− , map
55 , member
39 , member
56 , toList
40 , toList
57− , union
58 )
41 )
59import Data.Foldable (forM_)
42import Data.Foldable (forM_)
60−import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
43+import Data.Maybe (isJust, isNothing, catMaybes)
61import Data.Typeable (Typeable)
44import Data.Typeable (Typeable)
62import Control.Category ((>>>))
45import Control.Category ((>>>))
63−import Control.Applicative
46+import Control.Applicative ((<$>))
64−import Control.Monad (void, when, join)
47+import Control.Monad (void, when)
65import Control.Monad.IO.Class (MonadIO, liftIO)
48import Control.Monad.IO.Class (MonadIO, liftIO)
66import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
49import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
67import qualified Control.Monad.State.Strict as StateT (get, put)
50import qualified Control.Monad.State.Strict as StateT (get, put)
68import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
51import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
69−import Control.Exception
52+import Control.Exception (throwIO, SomeException, Exception, throwTo)
70− ( throwIO
71− , SomeException
72− , Exception
73− , throwTo
74− , uninterruptibleMask_
75− , getMaskingState
76− , MaskingState(..)
77− )
78−import qualified Control.Exception as Exception
53+import qualified Control.Exception as Exception (catch)
79− ( Handler(..)
80− , catch
81− , catches
82− , finally
83− )
84−import Control.Concurrent (forkIO, killThread)
54+import Control.Concurrent (forkIO)
85−import Control.Distributed.Process.Internal.BiMultiMap (BiMultiMap)
86−import qualified Control.Distributed.Process.Internal.BiMultiMap as BiMultiMap
87import Control.Distributed.Process.Internal.StrictMVar
55import Control.Distributed.Process.Internal.StrictMVar
88 ( newMVar
56 ( newMVar
89 , withMVar
57 , withMVar
90− , modifyMVarMasked
91 , modifyMVar
58 , modifyMVar
59+ , modifyMVar_
92 , newEmptyMVar
60 , newEmptyMVar
93 , putMVar
61 , putMVar
94 , takeMVar
62 , takeMVar
95 )
63 )
96import Control.Concurrent.Chan (newChan, writeChan, readChan)
64import Control.Concurrent.Chan (newChan, writeChan, readChan)
97−import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
98import Control.Concurrent.STM
65import Control.Concurrent.STM
99 ( atomically
66 ( atomically
100 )
67 )
@@ -103,7 +70,6 @@ import Control.Distributed.Process.Internal.CQueue
103 , enqueue
70 , enqueue
104 , newCQueue
71 , newCQueue
105 , mkWeakCQueue
72 , mkWeakCQueue
106− , queueSize
107 )
73 )
108import qualified Network.Transport as NT
74import qualified Network.Transport as NT
109 ( Transport
75 ( Transport
@@ -115,8 +81,8 @@ import qualified Network.Transport as NT
115 , TransportError(..)
81 , TransportError(..)
116 , address
82 , address
117 , closeEndPoint
83 , closeEndPoint
118− , Connection
119 , ConnectionId
84 , ConnectionId
85+ , Connection
120 , close
86 , close
121 , EndPointAddress
87 , EndPointAddress
122 , Reliability(ReliableOrdered)
88 , Reliability(ReliableOrdered)
@@ -133,11 +99,8 @@ import Control.Distributed.Process.Internal.Types
133 , LocalProcessId(..)
99 , LocalProcessId(..)
134 , ProcessId(..)
100 , ProcessId(..)
135 , LocalNode(..)
101 , LocalNode(..)
136− , MxEventBus(..)
102+ , Tracer
137 , LocalNodeState(..)
103 , LocalNodeState(..)
138− , ValidLocalNodeState(..)
139− , withValidLocalState
140− , modifyValidLocalState
141 , LocalProcess(..)
104 , LocalProcess(..)
142 , LocalProcessState(..)
105 , LocalProcessState(..)
143 , Process(..)
106 , Process(..)
@@ -147,11 +110,9 @@ import Control.Distributed.Process.Internal.Types
147 , localPidCounter
110 , localPidCounter
148 , localPidUnique
111 , localPidUnique
149 , localProcessWithId
112 , localProcessWithId
150− , localProcesses
151 , localConnections
113 , localConnections
152 , forever'
114 , forever'
153 , MonitorRef(..)
115 , MonitorRef(..)
154− , NodeClosedException(..)
155 , ProcessMonitorNotification(..)
116 , ProcessMonitorNotification(..)
156 , NodeMonitorNotification(..)
117 , NodeMonitorNotification(..)
157 , PortMonitorNotification(..)
118 , PortMonitorNotification(..)
@@ -165,87 +126,58 @@ import Control.Distributed.Process.Internal.Types
165 , DidUnlinkPort(..)
126 , DidUnlinkPort(..)
166 , SpawnRef
127 , SpawnRef
167 , DidSpawn(..)
128 , DidSpawn(..)
168− , Message(..)
129+ , Message
169 , TypedChannel(..)
130 , TypedChannel(..)
170 , Identifier(..)
131 , Identifier(..)
171 , nodeOf
132 , nodeOf
172 , ProcessInfo(..)
133 , ProcessInfo(..)
173 , ProcessInfoNone(..)
134 , ProcessInfoNone(..)
174− , NodeStats(..)
175 , SendPortId(..)
135 , SendPortId(..)
176 , typedChannelWithId
136 , typedChannelWithId
177 , RegisterReply(..)
137 , RegisterReply(..)
178 , WhereIsReply(..)
138 , WhereIsReply(..)
139+ , messageToPayload
179 , payloadToMessage
140 , payloadToMessage
180− , createUnencodedMessage
141+ , createMessage
181− , unsafeCreateUnencodedMessage
182 , runLocalProcess
142 , runLocalProcess
183 , firstNonReservedProcessId
143 , firstNonReservedProcessId
184− , ImplicitReconnect(WithImplicitReconnect)
144+ , ImplicitReconnect(WithImplicitReconnect, NoImplicitReconnect)
185− )
186−import Control.Distributed.Process.Management.Internal.Agent
187− ( mxAgentController
188− )
189−import Control.Distributed.Process.Management.Internal.Types
190− ( MxEvent(..)
191− )
192−import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
193− ( remoteTable
194− )
195−import Control.Distributed.Process.Management.Internal.Trace.Tracer
196− ( defaultTracer
197 )
145 )
198−import Control.Distributed.Process.Management.Internal.Trace.Types
146+import Control.Distributed.Process.Internal.Trace
199− ( TraceArg(..)
147+ ( trace
200− , traceEvent
148+ , traceFormat
201− , traceLogFmt
149+ , defaultTracer
202− , enableTrace
150+ , stopTracer
203 )
151 )
204import Control.Distributed.Process.Serializable (Serializable)
152import Control.Distributed.Process.Serializable (Serializable)
205import Control.Distributed.Process.Internal.Messaging
153import Control.Distributed.Process.Internal.Messaging
206 ( sendBinary
154 ( sendBinary
155+ , sendMessage
156+ , sendPayload
207 , closeImplicitReconnections
157 , closeImplicitReconnections
208 , impliesDeathOf
158 , impliesDeathOf
209 )
159 )
210import Control.Distributed.Process.Internal.Primitives
160import Control.Distributed.Process.Internal.Primitives
211 ( register
161 ( register
162+ , finally
212 , receiveWait
163 , receiveWait
213 , match
164 , match
214 , sendChan
165 , sendChan
215− , unwrapMessage
216− , monitor
217− , unmonitorAsync
218− , getSelfNode
219− , SayMessage(..)
220 )
166 )
221−import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
167+import Control.Distributed.Process.Internal.Types (SendPort)
222import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
168import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
223−import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
169+import Control.Distributed.Process.Internal.WeakTQueue (writeTQueue)
224import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
170import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
225 ( mapMaybe
171 ( mapMaybe
226 , mapDefault
172 , mapDefault
227 )
173 )
228−import Control.Monad.Catch (try)
229−import GHC.IO (IO(..), unsafeUnmask)
230−import GHC.Base ( maskAsyncExceptions# )
231−
232−import Unsafe.Coerce
233−import Prelude
234−
235−-- Remove these definitions when the fix for
236−-- https://ghc.haskell.org/trac/ghc/ticket/10149
237−-- is included in all supported compilers:
238−block :: IO a -> IO a
239−block (IO io) = IO $ maskAsyncExceptions# io
240−unblock :: IO a -> IO a
241−unblock = unsafeUnmask
242
174
243--------------------------------------------------------------------------------
175--------------------------------------------------------------------------------
244-- Initialization --
176-- Initialization --
245--------------------------------------------------------------------------------
177--------------------------------------------------------------------------------
246
178
247initRemoteTable :: RemoteTable
179initRemoteTable :: RemoteTable
248−initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
180+initRemoteTable = BuiltIn.remoteTable Static.initRemoteTable
249
181
250-- | Initialize a new local node.
182-- | Initialize a new local node.
251newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
183newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
@@ -261,133 +193,64 @@ newLocalNode transport rtable = do
261-- | Create a new local node (without any service processes running)
193-- | Create a new local node (without any service processes running)
262createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
194createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
263createBareLocalNode endPoint rtable = do
195createBareLocalNode endPoint rtable = do
264− unq <- randomIO
196+ unq <- randomIO
265− state <- newMVar $ LocalNodeValid $ ValidLocalNodeState
197+ state <- newMVar LocalNodeState
266− { _localProcesses = Map.empty
198+ { _localProcesses = Map.empty
267− , _localPidCounter = firstNonReservedProcessId
199+ , _localPidCounter = firstNonReservedProcessId
268− , _localPidUnique = unq
200+ , _localPidUnique = unq
269− , _localConnections = Map.empty
201+ , _localConnections = Map.empty
270− }
202+ }
203+ tracer <- defaultTracer
271− ctrlChan <- newChan
204+ ctrlChan <- newChan
272− let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
205+ let node = LocalNode { localNodeId = NodeId $ NT.address endPoint
273− , localEndPoint = endPoint
206+ , localEndPoint = endPoint
274− , localState = state
207+ , localState = state
275− , localCtrlChan = ctrlChan
208+ , localCtrlChan = ctrlChan
276− , localEventBus = MxEventBusInitialising
209+ , localTracer = tracer
277− , remoteTable = rtable
210+ , remoteTable = rtable
278− }
211+ }
279− tracedNode <- startMxAgent node
280−
281− -- Once the NC terminates, the endpoint isn't much use,
282− void $ forkIO $ Exception.finally (runNodeController tracedNode)
212+ void . forkIO $ runNodeController node
283− (NT.closeEndPoint (localEndPoint node))
284−
285− -- whilst a closed/failing endpoint will terminate the NC
286− void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
213+ void . forkIO $ handleIncomingMessages node
287− (stopNC node)
214+ return node
288−
289− return tracedNode
290− where
291− stopNC node =
292− writeChan (localCtrlChan node) NCMsg
293− { ctrlMsgSender = NodeIdentifier (localNodeId node)
294− , ctrlMsgSignal = SigShutdown
295− }
296−
297−startMxAgent :: LocalNode -> IO LocalNode
298−startMxAgent node = do
299− -- see note [tracer/forkProcess races]
300− let fork = forkProcess node
301− mv <- MVar.newEmptyMVar
302− pid <- fork $ mxAgentController fork mv
303− (tracer', wqRef, mxNew') <- MVar.takeMVar mv
304− return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }
305−
306−startDefaultTracer :: LocalNode -> IO ()
307−startDefaultTracer node' = do
308− let t = localEventBus node'
309− case t of
310− MxEventBus _ (Tracer pid _) _ _ -> do
311− runProcess node' $ register "trace.controller" pid
312− pid' <- forkProcess node' defaultTracer
313− enableTrace (localEventBus node') pid'
314− runProcess node' $ register "tracer.initial" pid'
315− _ -> return ()
316−
317−-- TODO: we need a better mechanism for defining and registering services
318
215
319-- | Start and register the service processes on a node
216-- | Start and register the service processes on a node
217+-- (for now, this is only the logger)
320startServiceProcesses :: LocalNode -> IO ()
218startServiceProcesses :: LocalNode -> IO ()
321startServiceProcesses node = do
219startServiceProcesses node = do
322− -- tracing /spawns/ relies on the tracer being enabled, but we start
323− -- the default tracer first, even though it might @nsend@ to the logger
324− -- before /that/ process has started - this is a totally harmless race
325− -- however, so we deliberably ignore it
326− startDefaultTracer node
327 logger <- forkProcess node loop
220 logger <- forkProcess node loop
328− runProcess node $ do
329− register "logger" logger
330− -- The trace.logger is used for tracing to the console to avoid feedback
331− -- loops during tracing if the user reregisters the "logger" with a custom
332− -- process which uses 'send' or other primitives which are traced.
333− register "trace.logger" logger
221+ runProcess node $ register "logger" logger
334 where
222 where
335− loop = do
223+ loop = do
336− receiveWait
224+ receiveWait
337− [ match $ \(SayMessage time pid string) -> do
225+ [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
338− let time' = formatTime defaultTimeLocale "%c" time
339− liftIO . hPutStrLn stderr $ time' ++ " " ++ show pid ++ ": " ++ string
226+ liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
340− loop
341− , match $ \((time, string) :: (String, String)) -> do
342− -- this is a 'trace' message from the local node tracer
343− liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
344− loop
227+ loop
345− , match $ \(ch :: SendPort ()) -> -- a shutdown request
228+ , match $ \(ch :: SendPort ()) -> -- a shutdown request
346− sendChan ch ()
229+ sendChan ch ()
347− ]
230+ ]
348−
231+
349−-- | Force-close a local node, killing all processes on that node.
232+-- | Force-close a local node
233+--
234+-- TODO: for now we just close the associated endpoint
350closeLocalNode :: LocalNode -> IO ()
235closeLocalNode :: LocalNode -> IO ()
351−closeLocalNode node = do
236+closeLocalNode node =
352− -- Kill processes after refilling the mvar. Otherwise, there is potential for
353− -- deadlock as a dying process tries to get the mvar while masking exceptions
354− -- uninterruptibly.
355− join $ modifyMVar (localState node) $ \st -> case st of
356− LocalNodeValid vst -> do
357− return ( LocalNodeClosed
358− , forM_ (vst ^. localProcesses) $ \lproc ->
359− -- Semantics of 'throwTo' guarantee that target thread will get
360− -- delivered an exception. Therefore, target thread will be
361− -- killed eventually and that's as good as we can do. No need
362− -- to wait for thread to actually finish dying.
363− killThread (processThread lproc)
364− )
365− LocalNodeClosed -> return (LocalNodeClosed, return ())
366− -- This call will have the effect of shutting down the NC as well (see
367− -- 'createBareLocalNode').
368 NT.closeEndPoint (localEndPoint node)
237 NT.closeEndPoint (localEndPoint node)
369
238
370-- | Run a process on a local node and wait for it to finish
239-- | Run a process on a local node and wait for it to finish
371runProcess :: LocalNode -> Process () -> IO ()
240runProcess :: LocalNode -> Process () -> IO ()
372runProcess node proc = do
241runProcess node proc = do
373 done <- newEmptyMVar
242 done <- newEmptyMVar
374− -- TODO; When forkProcess inherits the masking state, protect the forked
375− -- thread against async exceptions that could occur before 'try' is evaluated.
376− void $ forkProcess node $ try proc >>= liftIO . putMVar done
243+ void $ forkProcess node (proc `finally` liftIO (putMVar done ()))
377− takeMVar done >>= either (throwIO :: SomeException -> IO a) return
244+ takeMVar done
378
245
379-- | Spawn a new process on a local node
246-- | Spawn a new process on a local node
380forkProcess :: LocalNode -> Process () -> IO ProcessId
247forkProcess :: LocalNode -> Process () -> IO ProcessId
381−forkProcess node proc = do
382− ms <- getMaskingState
383− modifyMVarMasked (localState node) (startProcess ms)
248+forkProcess node proc = modifyMVar (localState node) startProcess
384 where
249 where
385− startProcess :: MaskingState
386− -> LocalNodeState
387− -> IO (LocalNodeState, ProcessId)
250+ startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
388− startProcess ms (LocalNodeValid vst) = do
251+ startProcess st = do
389− let lpid = LocalProcessId { lpidCounter = vst ^. localPidCounter
252+ let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
390− , lpidUnique = vst ^. localPidUnique
253+ , lpidUnique = st ^. localPidUnique
391 }
254 }
392 let pid = ProcessId { processNodeId = localNodeId node
255 let pid = ProcessId { processNodeId = localNodeId node
393 , processLocalId = lpid
256 , processLocalId = lpid
@@ -407,81 +270,43 @@ forkProcess node proc = do
407 , processThread = tid
270 , processThread = tid
408 , processNode = node
271 , processNode = node
409 }
272 }
410− -- Rewrite this code when this is fixed:
411− -- https://ghc.haskell.org/trac/ghc/ticket/10149
412− let unmask = case ms of
413− Unmasked -> unblock
414− MaskedInterruptible -> block
415− MaskedUninterruptible -> id
416− tid' <- uninterruptibleMask_ $ forkIO $ do
273+ tid' <- forkIO $ do
417− reason <- Exception.catches
274+ reason <- Exception.catch
418− (unmask $ runLocalProcess lproc proc >> return DiedNormal)
275+ (runLocalProcess lproc proc >> return DiedNormal)
419− [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
420− mMsg <- unwrapMessage msg :: IO (Maybe String)
421− case mMsg of
422− Nothing -> return $ DiedException $ show ex
423− Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
424− , (Exception.Handler
425− (return . DiedException . (show :: SomeException -> String)))]
276+ (return . DiedException . (show :: SomeException -> String))
426
277
427 -- [Unified: Table 4, rules termination and exiting]
278 -- [Unified: Table 4, rules termination and exiting]
428− mconns <- modifyValidLocalState node (cleanupProcess pid)
279+ modifyMVar_ (localState node) (cleanupProcess pid)
429− -- XXX: Revisit after agreeing on the bigger picture for the semantics
430− -- of transport operations.
431− -- https://github.com/haskell-distributed/distributed-process/issues/204
432− forM_ mconns $ forkIO . mapM_ NT.close
433−
434 writeChan (localCtrlChan node) NCMsg
280 writeChan (localCtrlChan node) NCMsg
435 { ctrlMsgSender = ProcessIdentifier pid
281 { ctrlMsgSender = ProcessIdentifier pid
436 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
282 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
437 }
283 }
438 return (tid', lproc)
284 return (tid', lproc)
439
285
440− -- see note [tracer/forkProcess races]
441− trace node (MxSpawned pid)
442−
443 if lpidCounter lpid == maxBound
286 if lpidCounter lpid == maxBound
444 then do
287 then do
445− -- TODO: this doesn't look right at all - how do we know
446− -- that newUnique represents a process id that is available!?
447 newUnique <- randomIO
288 newUnique <- randomIO
448− return ( LocalNodeValid
449− $ (localProcessWithId lpid ^= Just lproc)
289+ return ( (localProcessWithId lpid ^= Just lproc)
450 . (localPidCounter ^= firstNonReservedProcessId)
290 . (localPidCounter ^= firstNonReservedProcessId)
451 . (localPidUnique ^= newUnique)
291 . (localPidUnique ^= newUnique)
452− $ vst
292+ $ st
453 , pid
293 , pid
454 )
294 )
455 else
295 else
456− return ( LocalNodeValid
457− $ (localProcessWithId lpid ^= Just lproc)
296+ return ( (localProcessWithId lpid ^= Just lproc)
458 . (localPidCounter ^: (+ 1))
297 . (localPidCounter ^: (+ 1))
459− $ vst
298+ $ st
460 , pid
299 , pid
461 )
300 )
462− startProcess _ LocalNodeClosed =
463− throwIO $ NodeClosedException $ localNodeId node
464
301
465− cleanupProcess :: ProcessId
302+ cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
466− -> ValidLocalNodeState
467− -> IO (ValidLocalNodeState, [NT.Connection])
468− cleanupProcess pid vst = do
303+ cleanupProcess pid st = do
469 let pid' = ProcessIdentifier pid
304 let pid' = ProcessIdentifier pid
470− let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (vst ^. localConnections)
305+ let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
306+ mapM_ (NT.close . fst) (Map.elems affected)
471− return ( (localProcessWithId (processLocalId pid) ^= Nothing)
307+ return $ (localProcessWithId (processLocalId pid) ^= Nothing)
472− . (localConnections ^= unaffected)
308+ . (localConnections ^= unaffected)
473− $ vst
309+ $ st
474− , map fst $ Map.elems affected
475− )
476−
477−-- note [tracer/forkProcess races]
478−--
479−-- Our startTracing function uses forkProcess to start the trace controller
480−-- process, and of course forkProcess attempts to call traceEvent once the
481−-- process has started. This is harmless, as the localEventBus is not updated
482−-- until /after/ the initial forkProcess completes, so the first call to
483−-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
484−--
485
310
486--------------------------------------------------------------------------------
311--------------------------------------------------------------------------------
487-- Handle incoming messages --
312-- Handle incoming messages --
@@ -491,8 +316,8 @@ type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
491
316
492data IncomingTarget =
317data IncomingTarget =
493 Uninit
318 Uninit
494− | ToProc ProcessId (Weak (CQueue Message))
319+ | ToProc (Weak (CQueue Message))
495− | ToChan SendPortId TypedChannel
320+ | ToChan TypedChannel
496 | ToNode
321 | ToNode
497
322
498data ConnectionState = ConnectionState {
323data ConnectionState = ConnectionState {
@@ -519,7 +344,6 @@ incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
519
344
520handleIncomingMessages :: LocalNode -> IO ()
345handleIncomingMessages :: LocalNode -> IO ()
521handleIncomingMessages node = go initConnectionState
346handleIncomingMessages node = go initConnectionState
522− `Exception.catch` \(NodeClosedException _) -> return ()
523 where
347 where
524 go :: ConnectionState -> IO ()
348 go :: ConnectionState -> IO ()
525 go !st = do
349 go !st = do
@@ -527,37 +351,28 @@ handleIncomingMessages node = go initConnectionState
527 case event of
351 case event of
528 NT.ConnectionOpened cid rel theirAddr ->
352 NT.ConnectionOpened cid rel theirAddr ->
529 if rel == NT.ReliableOrdered
353 if rel == NT.ReliableOrdered
530− then
531− trace node (MxConnected cid theirAddr)
532− >> go (
533− (incomingAt cid ^= Just (theirAddr, Uninit))
354+ then go ( (incomingAt cid ^= Just (theirAddr, Uninit))
534 . (incomingFrom theirAddr ^: Set.insert cid)
355 . (incomingFrom theirAddr ^: Set.insert cid)
535 $ st
356 $ st
536 )
357 )
537− else invalidRequest cid st $
358+ else invalidRequest cid st
538− "attempt to connect with unsupported reliability " ++ show rel
539 NT.Received cid payload ->
359 NT.Received cid payload ->
540 case st ^. incomingAt cid of
360 case st ^. incomingAt cid of
541− Just (_, ToProc pid weakQueue) -> do
361+ Just (_, ToProc weakQueue) -> do
542 mQueue <- deRefWeak weakQueue
362 mQueue <- deRefWeak weakQueue
543 forM_ mQueue $ \queue -> do
363 forM_ mQueue $ \queue -> do
544 -- TODO: if we find that the queue is Nothing, should we remove
364 -- TODO: if we find that the queue is Nothing, should we remove
545 -- it from the NC state? (and same for channels, below)
365 -- it from the NC state? (and same for channels, below)
546 let msg = payloadToMessage payload
366 let msg = payloadToMessage payload
547 enqueue queue msg -- 'enqueue' is strict
367 enqueue queue msg -- 'enqueue' is strict
548− trace node (MxReceived pid msg)
549 go st
368 go st
550− Just (_, ToChan chId (TypedChannel chan')) -> do
369+ Just (_, ToChan (TypedChannel chan')) -> do
551 mChan <- deRefWeak chan'
370 mChan <- deRefWeak chan'
552 -- If mChan is Nothing, the process has given up the read end of
371 -- If mChan is Nothing, the process has given up the read end of
553 -- the channel and we simply ignore the incoming message
372 -- the channel and we simply ignore the incoming message
554− forM_ mChan $ \chan -> do
373+ forM_ mChan $ \chan -> atomically $
555− msg' <- atomically $ do
556− msg <- return $! decode (BSL.fromChunks payload)
557− -- We make sure the message is fully decoded when it is enqueued
374+ -- We make sure the message is fully decoded when it is enqueued
558− writeTQueue chan msg
375+ writeTQueue chan $! decode (BSL.fromChunks payload)
559− return msg
560− trace node $ MxReceivedPort chId $ unsafeCreateUnencodedMessage msg'
561 go st
376 go st
562 Just (_, ToNode) -> do
377 Just (_, ToNode) -> do
563 let ctrlMsg = decode . BSL.fromChunks $ payload
378 let ctrlMsg = decode . BSL.fromChunks $ payload
@@ -567,49 +382,37 @@ handleIncomingMessages node = go initConnectionState
567 case decode (BSL.fromChunks payload) of
382 case decode (BSL.fromChunks payload) of
568 ProcessIdentifier pid -> do
383 ProcessIdentifier pid -> do
569 let lpid = processLocalId pid
384 let lpid = processLocalId pid
570− mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
385+ mProc <- withMVar state $ return . (^. localProcessWithId lpid)
571 case mProc of
386 case mProc of
572 Just proc ->
387 Just proc ->
573− go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
388+ go (incomingAt cid ^= Just (src, ToProc (processWeakQ proc)) $ st)
574 Nothing ->
389 Nothing ->
575− -- incoming attempt to connect to unknown process - might
390+ invalidRequest cid st
576− -- be dead already
577− go (incomingAt cid ^= Nothing $ st)
578 SendPortIdentifier chId -> do
391 SendPortIdentifier chId -> do
579 let lcid = sendPortLocalId chId
392 let lcid = sendPortLocalId chId
580 lpid = processLocalId (sendPortProcessId chId)
393 lpid = processLocalId (sendPortProcessId chId)
581− mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
394+ mProc <- withMVar state $ return . (^. localProcessWithId lpid)
582 case mProc of
395 case mProc of
583 Just proc -> do
396 Just proc -> do
584 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
397 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
585 case mChannel of
398 case mChannel of
586 Just channel ->
399 Just channel ->
587− go (incomingAt cid ^= Just (src, ToChan chId channel) $ st)
400+ go (incomingAt cid ^= Just (src, ToChan channel) $ st)
588 Nothing ->
401 Nothing ->
589− invalidRequest cid st $
402+ invalidRequest cid st
590− "incoming attempt to connect to unknown channel of"
591− ++ " process " ++ show (sendPortProcessId chId)
592 Nothing ->
403 Nothing ->
593− -- incoming attempt to connect to channel of unknown
404+ invalidRequest cid st
594− -- process - might be dead already
595− go (incomingAt cid ^= Nothing $ st)
596 NodeIdentifier nid ->
405 NodeIdentifier nid ->
597 if nid == localNodeId node
406 if nid == localNodeId node
598 then go (incomingAt cid ^= Just (src, ToNode) $ st)
407 then go (incomingAt cid ^= Just (src, ToNode) $ st)
599− else invalidRequest cid st $
408+ else invalidRequest cid st
600− "incoming attempt to connect to a different node -"
601− ++ " I'm " ++ show (localNodeId node)
602− ++ " but the remote peer wants to connect to "
603− ++ show nid
604 Nothing ->
409 Nothing ->
605 invalidRequest cid st
410 invalidRequest cid st
606− "message received from an unknown connection"
607 NT.ConnectionClosed cid ->
411 NT.ConnectionClosed cid ->
608 case st ^. incomingAt cid of
412 case st ^. incomingAt cid of
609 Nothing ->
413 Nothing ->
610− invalidRequest cid st "closed unknown connection"
414+ invalidRequest cid st
611− Just (src, _) -> do
415+ Just (src, _) ->
612− trace node (MxDisconnected cid src)
613 go ( (incomingAt cid ^= Nothing)
416 go ( (incomingAt cid ^= Nothing)
614 . (incomingFrom src ^: Set.delete cid)
417 . (incomingFrom src ^: Set.delete cid)
615 $ st
418 $ st
@@ -638,20 +441,18 @@ handleIncomingMessages node = go initConnectionState
638 -- and we just give up
441 -- and we just give up
639 fail "Cloud Haskell fatal error: received unexpected multicast"
442 fail "Cloud Haskell fatal error: received unexpected multicast"
640
443
641− invalidRequest :: NT.ConnectionId -> ConnectionState -> String -> IO ()
444+ invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
642− invalidRequest cid st msg = do
445+ invalidRequest cid st = do
643 -- TODO: We should treat this as a fatal error on the part of the remote
446 -- TODO: We should treat this as a fatal error on the part of the remote
644 -- node. That is, we should report the remote node as having died, and we
447 -- node. That is, we should report the remote node as having died, and we
645 -- should close incoming connections (this requires a Transport layer
448 -- should close incoming connections (this requires a Transport layer
646 -- extension).
449 -- extension).
647− traceEventFmtIO node "" [ TraceStr $ " [network] invalid request"
450+ traceEventFmtIO node "" ["[network] invalid request: ", (show cid)]
648− ++ " (" ++ msg ++ "): "
649− , (Trace cid)
650− ]
651 go ( incomingAt cid ^= Nothing
451 go ( incomingAt cid ^= Nothing
652 $ st
452 $ st
653 )
453 )
654
454
455+ state = localState node
655 endpoint = localEndPoint node
456 endpoint = localEndPoint node
656 ctrlChan = localCtrlChan node
457 ctrlChan = localCtrlChan node
657
458
@@ -660,9 +461,8 @@ handleIncomingMessages node = go initConnectionState
660--------------------------------------------------------------------------------
461--------------------------------------------------------------------------------
661
462
662runNodeController :: LocalNode -> IO ()
463runNodeController :: LocalNode -> IO ()
663−runNodeController node =
464+runNodeController =
664− runReaderT (evalStateT (unNC nodeController) initNCState) node
465+ runReaderT (evalStateT (unNC nodeController) initNCState)
665− `Exception.catch` \(NodeClosedException _) -> return ()
666
466
667--------------------------------------------------------------------------------
467--------------------------------------------------------------------------------
668-- Internal data types --
468-- Internal data types --
@@ -670,26 +470,20 @@ runNodeController node =
670
470
671data NCState = NCState
471data NCState = NCState
672 { -- Mapping from remote processes to linked local processes
472 { -- Mapping from remote processes to linked local processes
673− _links :: !(BiMultiMap Identifier ProcessId ())
473+ _links :: !(Map Identifier (Set ProcessId))
674 -- Mapping from remote processes to monitoring local processes
474 -- Mapping from remote processes to monitoring local processes
675− , _monitors :: !(BiMultiMap Identifier ProcessId MonitorRef)
475+ , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
676 -- Process registry: names and where they live, mapped to the PIDs
476 -- Process registry: names and where they live, mapped to the PIDs
677 , _registeredHere :: !(Map String ProcessId)
477 , _registeredHere :: !(Map String ProcessId)
678 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
478 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
679 }
479 }
680
480
681newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
481newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
682− deriving ( Applicative
683− , Functor
684− , Monad
685− , MonadIO
686− , MonadState NCState
687− , MonadReader LocalNode
482+ deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
688− )
689
483
690initNCState :: NCState
484initNCState :: NCState
691−initNCState = NCState { _links = BiMultiMap.empty
485+initNCState = NCState { _links = Map.empty
692− , _monitors = BiMultiMap.empty
486+ , _monitors = Map.empty
693 , _registeredHere = Map.empty
487 , _registeredHere = Map.empty
694 , _registeredOnNodes = Map.empty
488 , _registeredOnNodes = Map.empty
695 }
489 }
@@ -705,60 +499,38 @@ instance Show ProcessKillException where
705 show (ProcessKillException pid reason) =
499 show (ProcessKillException pid reason) =
706 "killed-by=" ++ show pid ++ ",reason=" ++ reason
500 "killed-by=" ++ show pid ++ ",reason=" ++ reason
707
501
708−ncSendToProcess :: ProcessId -> Message -> NC ()
709−ncSendToProcess = ncSendToProcessAndTrace True
710−
711−ncSendToProcessAndTrace :: Bool -> ProcessId -> Message -> NC ()
712−ncSendToProcessAndTrace shouldTrace pid msg = do
713− node <- ask
714− if processNodeId pid == localNodeId node
715− then ncEffectLocalSendAndTrace shouldTrace node pid msg
716− else liftIO $ sendBinary node
717− (NodeIdentifier $ localNodeId node)
718− (NodeIdentifier $ processNodeId pid)
719− WithImplicitReconnect
720− NCMsg { ctrlMsgSender = NodeIdentifier $ localNodeId node
721− , ctrlMsgSignal = UnreliableSend (processLocalId pid) msg
722− }
723−
724−ncSendToNode :: NodeId -> NCMsg -> NC ()
725−ncSendToNode to msg = do
726− node <- ask
727− liftIO $ if to == localNodeId node
728− then writeChan (localCtrlChan node) $! msg
729− else sendBinary node
730− (NodeIdentifier $ localNodeId node)
731− (NodeIdentifier to)
732− WithImplicitReconnect
733− msg
734−
735--------------------------------------------------------------------------------
502--------------------------------------------------------------------------------
736-- Tracing/Debugging --
503-- Tracing/Debugging --
737--------------------------------------------------------------------------------
504--------------------------------------------------------------------------------
738
505
739−-- [Issue #104 / DP-13]
506+-- [Issue #104]
740
507
741traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
508traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
742traceNotifyDied node ident reason =
509traceNotifyDied node ident reason =
743− -- TODO: sendPortDied notifications
744− liftIO $ withLocalTracer node $ \t ->
745− case ident of
510+ case reason of
746− (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
511+ DiedNormal -> return ()
747− (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
512+ _ -> traceNcEventFmt node " "
748− _ -> return ()
513+ ["[node-controller]", (show ident), (show reason)]
514+
515+traceNcEvent :: LocalNode -> String -> NC ()
516+traceNcEvent node msg = liftIO $ traceEventIO node msg
517+
518+traceNcEventFmt :: LocalNode -> String -> [String] -> NC ()
519+traceNcEventFmt node fmt args =
520+ liftIO $ traceEventFmtIO node fmt args
521+
522+traceEventIO :: LocalNode -> String -> IO ()
523+traceEventIO node msg = withLocalTracer node $ \t -> trace t msg
749
524
750traceEventFmtIO :: LocalNode
525traceEventFmtIO :: LocalNode
751 -> String
526 -> String
752− -> [TraceArg]
527+ -> [String]
753 -> IO ()
528 -> IO ()
754traceEventFmtIO node fmt args =
529traceEventFmtIO node fmt args =
755− withLocalTracer node $ \t -> traceLogFmt t fmt args
530+ withLocalTracer node $ \t -> traceFormat t fmt args
756
531
757−trace :: LocalNode -> MxEvent -> IO ()
532+withLocalTracer :: LocalNode -> (Tracer -> IO ()) -> IO ()
758−trace node ev = withLocalTracer node $ \t -> traceEvent t ev
759−
760−withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
761−withLocalTracer node act = act (localEventBus node)
533+withLocalTracer node act = act (localTracer node)
762
534
763--------------------------------------------------------------------------------
535--------------------------------------------------------------------------------
764-- Core functionality --
536-- Core functionality --
@@ -774,7 +546,11 @@ nodeController = do
774 -- [Unified: Table 7, rule nc_forward]
546 -- [Unified: Table 7, rule nc_forward]
775 case destNid (ctrlMsgSignal msg) of
547 case destNid (ctrlMsgSignal msg) of
776 Just nid' | nid' /= localNodeId node ->
548 Just nid' | nid' /= localNodeId node ->
777− ncSendToNode nid' msg
549+ liftIO $ sendBinary node
550+ (ctrlMsgSender msg)
551+ (NodeIdentifier nid')
552+ WithImplicitReconnect
553+ msg
778 _ ->
554 _ ->
779 return ()
555 return ()
780
556
@@ -795,26 +571,14 @@ nodeController = do
795 ncEffectRegister from label atnode pid force
571 ncEffectRegister from label atnode pid force
796 NCMsg (ProcessIdentifier from) (WhereIs label) ->
572 NCMsg (ProcessIdentifier from) (WhereIs label) ->
797 ncEffectWhereIs from label
573 ncEffectWhereIs from label
798− NCMsg _ (NamedSend label msg') ->
574+ NCMsg from (NamedSend label msg') ->
799− ncEffectNamedSend label msg'
575+ ncEffectNamedSend from label msg'
800− NCMsg _ (UnreliableSend lpid msg') ->
801− ncEffectLocalSend node (ProcessId (localNodeId node) lpid) msg'
802− NCMsg _ (LocalSend to msg') ->
803− ncEffectLocalSend node to msg'
804− NCMsg _ (LocalPortSend to msg') ->
805− ncEffectLocalPortSend to msg'
806 NCMsg (ProcessIdentifier from) (Kill to reason) ->
576 NCMsg (ProcessIdentifier from) (Kill to reason) ->
807 ncEffectKill from to reason
577 ncEffectKill from to reason
808 NCMsg (ProcessIdentifier from) (Exit to reason) ->
578 NCMsg (ProcessIdentifier from) (Exit to reason) ->
809 ncEffectExit from to reason
579 ncEffectExit from to reason
810 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
580 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
811 ncEffectGetInfo from pid
581 ncEffectGetInfo from pid
812− NCMsg _ SigShutdown ->
813− liftIO $ do
814− NT.closeEndPoint (localEndPoint node)
815− `Exception.finally` throwIO (NodeClosedException $ localNodeId node)
816− NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
817− ncEffectGetNodeStats from nid
818 unexpected ->
582 unexpected ->
819 error $ "nodeController: unexpected message " ++ show unexpected
583 error $ "nodeController: unexpected message " ++ show unexpected
820
584
@@ -832,18 +596,22 @@ ncEffectMonitor from them mRef = do
832 case (shouldLink, isLocal node (ProcessIdentifier from)) of
596 case (shouldLink, isLocal node (ProcessIdentifier from)) of
833 (True, _) -> -- [Unified: first rule]
597 (True, _) -> -- [Unified: first rule]
834 case mRef of
598 case mRef of
835− Just ref -> modify' $ monitors ^: BiMultiMap.insert them from ref
599+ Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
836− Nothing -> modify' $ links ^: BiMultiMap.insert them from ()
600+ Nothing -> modify' $ linksFor them ^: Set.insert from
837 (False, True) -> -- [Unified: second rule]
601 (False, True) -> -- [Unified: second rule]
838 notifyDied from them DiedUnknownId mRef
602 notifyDied from them DiedUnknownId mRef
839 (False, False) -> -- [Unified: third rule]
603 (False, False) -> -- [Unified: third rule]
840 -- TODO: this is the right sender according to the Unified semantics,
604 -- TODO: this is the right sender according to the Unified semantics,
841 -- but perhaps having 'them' as the sender would make more sense
605 -- but perhaps having 'them' as the sender would make more sense
842 -- (see also: notifyDied)
606 -- (see also: notifyDied)
607+ liftIO $ sendBinary node
608+ (NodeIdentifier $ localNodeId node)
843− ncSendToNode (processNodeId from) $ NCMsg
609+ (NodeIdentifier $ processNodeId from)
610+ WithImplicitReconnect
611+ NCMsg
844− { ctrlMsgSender = NodeIdentifier (localNodeId node)
612+ { ctrlMsgSender = NodeIdentifier (localNodeId node)
845− , ctrlMsgSignal = Died them DiedUnknownId
613+ , ctrlMsgSignal = Died them DiedUnknownId
846− }
614+ }
847
615
848-- [Unified: Table 11]
616-- [Unified: Table 11]
849ncEffectUnlink :: ProcessId -> Identifier -> NC ()
617ncEffectUnlink :: ProcessId -> Identifier -> NC ()
@@ -857,7 +625,7 @@ ncEffectUnlink from them = do
857 postAsMessage from $ DidUnlinkNode nid
625 postAsMessage from $ DidUnlinkNode nid
858 SendPortIdentifier cid ->
626 SendPortIdentifier cid ->
859 postAsMessage from $ DidUnlinkPort cid
627 postAsMessage from $ DidUnlinkPort cid
860− modify' $ links ^: BiMultiMap.delete them from ()
628+ modify' $ linksFor them ^: Set.delete from
861
629
862-- [Unified: Table 11]
630-- [Unified: Table 11]
863ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
631ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
@@ -865,7 +633,7 @@ ncEffectUnmonitor from ref = do
865 node <- ask
633 node <- ask
866 when (isLocal node (ProcessIdentifier from)) $
634 when (isLocal node (ProcessIdentifier from)) $
867 postAsMessage from $ DidUnmonitor ref
635 postAsMessage from $ DidUnmonitor ref
868− modify' $ monitors ^: BiMultiMap.delete (monitorRefIdent ref) from ref
636+ modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
869
637
870-- [Unified: Table 12]
638-- [Unified: Table 12]
871ncEffectDied :: Identifier -> DiedReason -> NC ()
639ncEffectDied :: Identifier -> DiedReason -> NC ()
@@ -880,7 +648,7 @@ ncEffectDied ident reason = do
880 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
648 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
881
649
882 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
650 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
883− forM_ uss $ \(us, _) ->
651+ forM_ uss $ \us ->
884 when (localOnly <= isLocal node (ProcessIdentifier us)) $
652 when (localOnly <= isLocal node (ProcessIdentifier us)) $
885 notifyDied us them reason Nothing
653 notifyDied us them reason Nothing
886
654
@@ -889,35 +657,9 @@ ncEffectDied ident reason = do
889 when (localOnly <= isLocal node (ProcessIdentifier us)) $
657 when (localOnly <= isLocal node (ProcessIdentifier us)) $
890 notifyDied us them reason (Just ref)
658 notifyDied us them reason (Just ref)
891
659
892− -- Notify remote nodes that the process died so it can be removed from monitor
893− -- lists.
894− mapM_ (forwardDeath node) $
895− [ nid | ProcessIdentifier pid <- [ident]
896− , i <- Set.toList $ Set.union
897− (Set.map fst $ BiMultiMap.lookupBy2nd pid unaffectedLinks)
898− (Set.map fst $ BiMultiMap.lookupBy2nd pid unaffectedMons)
899− , let nid = nodeOf i
900− , nid /= localNodeId node
901− ]
902−
903− -- Delete monitors in the local node.
904− let deleteDeads :: (Ord a, Ord v)
905− => BiMultiMap a ProcessId v -> BiMultiMap a ProcessId v
906− deleteDeads = case ident of
907− -- deleteAllBy2nd is faster than partitionWithKeyBy2nd
908− ProcessIdentifier pid -> BiMultiMap.deleteAllBy2nd pid
909− _ -> snd . BiMultiMap.partitionWithKeyBy2nd
910− (\pid _ -> ident `impliesDeathOf` ProcessIdentifier pid)
911− unaffectedLinks' = deleteDeads unaffectedLinks
912− unaffectedMons' = deleteDeads unaffectedMons
913−
914− modify' $ (links ^= unaffectedLinks') . (monitors ^= unaffectedMons')
660+ modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
915−
661+
916− -- we now consider all labels for this identifier unregistered
917− let toDrop pid = not $ ident `impliesDeathOf` ProcessIdentifier pid
662+ modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
918− (keepNames, dropNames) <- Map.partition toDrop <$> gets (^. registeredHere)
919− mapM_ (\(p, l) -> liftIO $ trace node (MxUnRegistered l p)) (Map.toList dropNames)
920− modify' $ registeredHere ^= keepNames
921
663
922 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
664 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
923 mapM (\(pid,nidlist) ->
665 mapM (\(pid,nidlist) ->
@@ -925,15 +667,20 @@ ncEffectDied ident reason = do
925 True ->
667 True ->
926 do forM_ nidlist $ \(nid,_) ->
668 do forM_ nidlist $ \(nid,_) ->
927 when (not $ isLocal node (NodeIdentifier nid))
669 when (not $ isLocal node (NodeIdentifier nid))
928− (forwardDeath node nid)
670+ (forwardNameDeath node nid)
929 return Nothing
671 return Nothing
930 False -> return $ Just (pid,nidlist) )
672 False -> return $ Just (pid,nidlist) )
931 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
673 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
932 where
674 where
933− forwardDeath node nid = ncSendToNode nid
675+ forwardNameDeath node nid =
676+ liftIO $ sendBinary node
677+ (NodeIdentifier $ localNodeId node)
678+ (NodeIdentifier $ nid)
679+ WithImplicitReconnect
680+ NCMsg
934− NCMsg { ctrlMsgSender = NodeIdentifier (localNodeId node)
681+ { ctrlMsgSender = NodeIdentifier (localNodeId node)
935− , ctrlMsgSignal = Died ident reason
682+ , ctrlMsgSignal = Died ident reason
936− }
683+ }
937
684
938-- [Unified: Table 13]
685-- [Unified: Table 13]
939ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
686ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
@@ -941,13 +688,16 @@ ncEffectSpawn pid cProc ref = do
941 mProc <- unClosure cProc
688 mProc <- unClosure cProc
942 -- If the closure does not exist, we spawn a process that throws an exception
689 -- If the closure does not exist, we spawn a process that throws an exception
943 -- This allows the remote node to find out what's happening
690 -- This allows the remote node to find out what's happening
944− -- TODO:
945 let proc = case mProc of
691 let proc = case mProc of
946 Left err -> fail $ "Error: Could not resolve closure: " ++ err
692 Left err -> fail $ "Error: Could not resolve closure: " ++ err
947 Right p -> p
693 Right p -> p
948 node <- ask
694 node <- ask
949 pid' <- liftIO $ forkProcess node proc
695 pid' <- liftIO $ forkProcess node proc
950− ncSendToProcess pid $ unsafeCreateUnencodedMessage $ DidSpawn ref pid'
696+ liftIO $ sendMessage node
697+ (NodeIdentifier (localNodeId node))
698+ (ProcessIdentifier pid)
699+ WithImplicitReconnect
700+ (DidSpawn ref pid')
951
701
952-- Unified semantics does not explicitly describe how to implement 'register',
702-- Unified semantics does not explicitly describe how to implement 'register',
953-- but mentions it's "very similar to nsend" (Table 14)
703-- but mentions it's "very similar to nsend" (Table 14)
@@ -965,19 +715,14 @@ ncEffectRegister from label atnode mPid reregistration = do
965 return $ (isNothing currentVal /= reregistration) &&
715 return $ (isNothing currentVal /= reregistration) &&
966 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
716 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
967 if isLocal node (NodeIdentifier atnode)
717 if isLocal node (NodeIdentifier atnode)
968− then do when isOk $
718+ then do when (isOk) $
969 do modify' $ registeredHereFor label ^= mPid
719 do modify' $ registeredHereFor label ^= mPid
970 updateRemote node currentVal mPid
720 updateRemote node currentVal mPid
971− case mPid of
721+ liftIO $ sendMessage node
972− (Just p) -> do
722+ (NodeIdentifier (localNodeId node))
973− if reregistration
723+ (ProcessIdentifier from)
974− then liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
724+ WithImplicitReconnect
975− else return ()
976− liftIO $ trace node (MxRegistered p label)
977− Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
978− newVal <- gets (^. registeredHereFor label)
979− ncSendToProcess from $ unsafeCreateUnencodedMessage $
980− RegisterReply label isOk newVal
725+ (RegisterReply label isOk)
981 else let operation =
726 else let operation =
982 case reregistration of
727 case reregistration of
983 True -> flip decList
728 True -> flip decList
@@ -995,7 +740,6 @@ ncEffectRegister from label atnode mPid reregistration = do
995 updateRemote _ _ _ = return ()
740 updateRemote _ _ _ = return ()
996 maybeify f Nothing = unmaybeify $ f []
741 maybeify f Nothing = unmaybeify $ f []
997 maybeify f (Just x) = unmaybeify $ f x
742 maybeify f (Just x) = unmaybeify $ f x
998−
999 unmaybeify [] = Nothing
743 unmaybeify [] = Nothing
1000 unmaybeify x = Just x
744 unmaybeify x = Just x
1001 incList [] tag = [(tag,1)]
745 incList [] tag = [(tag,1)]
@@ -1007,61 +751,39 @@ ncEffectRegister from label atnode mPid reregistration = do
1007 decList (x:xs) tag = x:decList xs tag
751 decList (x:xs) tag = x:decList xs tag
1008 forward node to reg =
752 forward node to reg =
1009 when (not $ isLocal node (NodeIdentifier to)) $
753 when (not $ isLocal node (NodeIdentifier to)) $
754+ liftIO $ sendBinary node
755+ (ProcessIdentifier from)
756+ (NodeIdentifier to)
757+ WithImplicitReconnect
758+ NCMsg
1010− ncSendToNode to $ NCMsg { ctrlMsgSender = ProcessIdentifier from
759+ { ctrlMsgSender = ProcessIdentifier from
1011− , ctrlMsgSignal = reg
760+ , ctrlMsgSignal = reg
1012− }
761+ }
1013
762
1014
763
1015-- Unified semantics does not explicitly describe 'whereis'
764-- Unified semantics does not explicitly describe 'whereis'
1016ncEffectWhereIs :: ProcessId -> String -> NC ()
765ncEffectWhereIs :: ProcessId -> String -> NC ()
1017ncEffectWhereIs from label = do
766ncEffectWhereIs from label = do
767+ node <- ask
1018 mPid <- gets (^. registeredHereFor label)
768 mPid <- gets (^. registeredHereFor label)
769+ liftIO $ sendMessage node
770+ (NodeIdentifier (localNodeId node))
771+ (ProcessIdentifier from)
772+ WithImplicitReconnect
1019− ncSendToProcess from $ unsafeCreateUnencodedMessage $ WhereIsReply label mPid
773+ (WhereIsReply label mPid)
1020
774
1021-- [Unified: Table 14]
775-- [Unified: Table 14]
1022−ncEffectNamedSend :: String -> Message -> NC ()
776+ncEffectNamedSend :: Identifier -> String -> Message -> NC ()
1023−ncEffectNamedSend label msg = do
777+ncEffectNamedSend from label msg = do
778+ node <- ask
1024 mPid <- gets (^. registeredHereFor label)
779 mPid <- gets (^. registeredHereFor label)
1025 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
780 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
1026− forM_ mPid $ \to ->
781+ forM_ mPid $ \pid ->
1027− -- If this is a trace message we don't trace it to avoid entering a loop
1028− -- where trace messages produce more trace messages.
1029− ncSendToProcessAndTrace (label /= "trace.logger") to msg
1030−
1031−-- [Issue #DP-20]
1032−ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
1033−ncEffectLocalSend = ncEffectLocalSendAndTrace True
1034−
1035−ncEffectLocalSendAndTrace :: Bool -> LocalNode -> ProcessId -> Message -> NC ()
1036−ncEffectLocalSendAndTrace shouldTrace node to msg =
1037− liftIO $ withLocalProc node to $ \p -> do
782+ liftIO $ sendPayload node
1038− enqueue (processQueue p) msg
783+ from
1039− when shouldTrace $ trace node (MxReceived to msg)
784+ (ProcessIdentifier pid)
1040−
785+ NoImplicitReconnect
1041−-- [Issue #DP-20]
786+ (messageToPayload msg)
1042−ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
1043−ncEffectLocalPortSend from msg = do
1044− node <- ask
1045− let pid = sendPortProcessId from
1046− cid = sendPortLocalId from
1047− liftIO $ withLocalProc node pid $ \proc -> do
1048− mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
1049− case mChan of
1050− -- in the unlikely event we know nothing about this channel id,
1051− -- there's little to be done - perhaps some logging/tracing though...
1052− Nothing -> return ()
1053− Just (TypedChannel chan') -> do
1054− -- If ch is Nothing, the process has given up the read end of
1055− -- the channel and we simply ignore the incoming message - this
1056− ch <- deRefWeak chan'
1057− forM_ ch $ \chan -> deliverChan node from msg chan
1058− where deliverChan :: forall a . LocalNode -> SendPortId -> Message -> TQueue a -> IO ()
1059− deliverChan n p (UnencodedMessage _ raw) chan' = do
1060− atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
1061− trace n (MxReceivedPort p $ unsafeCreateUnencodedMessage raw)
1062− deliverChan _ _ (EncodedMessage _ _) _ =
1063− -- this will not happen unless someone screws with Primitives.hs
1064− error "invalid local channel delivery"
1065
787
1066-- [Issue #69]
788-- [Issue #69]
1067ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
789ncEffectKill :: ProcessId -> ProcessId -> String -> NC ()
@@ -1084,56 +806,46 @@ ncEffectGetInfo from pid =
1084 them = (ProcessIdentifier pid)
806 them = (ProcessIdentifier pid)
1085 in do
807 in do
1086 node <- ask
808 node <- ask
1087− mProc <- liftIO $ withValidLocalState node
809+ mProc <- liftIO $
1088− $ return . (^. localProcessWithId lpid)
810+ withMVar (localState node) $ return . (^. localProcessWithId lpid)
1089 case mProc of
811 case mProc of
1090 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
812 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
1091− from (ProcessInfoNone DiedUnknownId)
813+ from node (ProcessInfoNone DiedUnknownId)
1092− Just proc -> do
814+ Just _ -> do
1093− itsLinks <- Set.map fst . BiMultiMap.lookupBy1st them <$>
815+ itsLinks <- gets (^. linksFor them)
1094− gets (^. links)
816+ itsMons <- gets (^. monitorsFor them)
1095− itsMons <- BiMultiMap.lookupBy1st them <$> gets (^. monitors)
1096 registered <- gets (^. registeredHere)
817 registered <- gets (^. registeredHere)
1097− size <- liftIO $ queueSize $ processQueue $ proc
1098
818
1099 let reg = registeredNames registered
819 let reg = registeredNames registered
1100 dispatch (isLocal node (ProcessIdentifier from))
820 dispatch (isLocal node (ProcessIdentifier from))
1101 from
821 from
822+ node
1102 ProcessInfo {
823 ProcessInfo {
1103 infoNode = (processNodeId pid)
824 infoNode = (processNodeId pid)
1104 , infoRegisteredNames = reg
825 , infoRegisteredNames = reg
826+ -- we cannot populate this field
1105− , infoMessageQueueLength = size
827+ , infoMessageQueueLength = Nothing
1106− , infoMonitors = Set.toList itsMons
828+ , infoMonitors = Set.toList itsMons
1107− , infoLinks = Set.toList itsLinks
829+ , infoLinks = Set.toList itsLinks
1108 }
830 }
1109− where dispatch :: (Serializable a)
831+ where dispatch :: (Serializable a, Show a)
1110 => Bool
832 => Bool
1111 -> ProcessId
833 -> ProcessId
834+ -> LocalNode
1112 -> a
835 -> a
1113 -> NC ()
836 -> NC ()
1114− dispatch True dest pInfo = postAsMessage dest $ pInfo
837+ dispatch True dest _ pInfo = postAsMessage dest $ pInfo
1115− dispatch False dest pInfo =
838+ dispatch False dest node pInfo = do
1116− ncSendToProcess dest $ unsafeCreateUnencodedMessage pInfo
839+ liftIO $ sendMessage node
840+ (NodeIdentifier (localNodeId node))
841+ (ProcessIdentifier dest)
842+ WithImplicitReconnect
843+ pInfo
1117
844
1118 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
845 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1119 then (k:ks)
846 then (k:ks)
1120 else ks) []
847 else ks) []
1121
848
1122−ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
1123−ncEffectGetNodeStats from _nid = do
1124− node <- ask
1125− ncState <- StateT.get
1126− nodeState <- liftIO $ withValidLocalState node return
1127− let stats =
1128− NodeStats {
1129− nodeStatsNode = localNodeId node
1130− , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1131− , nodeStatsMonitors = BiMultiMap.size $ ncState ^. monitors
1132− , nodeStatsLinks = BiMultiMap.size $ ncState ^. links
1133− , nodeStatsProcesses = Map.size (nodeState ^. localProcesses)
1134− }
1135− postAsMessage from stats
1136−
1137--------------------------------------------------------------------------------
849--------------------------------------------------------------------------------
1138-- Auxiliary --
850-- Auxiliary --
1139--------------------------------------------------------------------------------
851--------------------------------------------------------------------------------
@@ -1160,33 +872,32 @@ notifyDied dest src reason mRef = do
1160 throwException dest $ PortLinkException pid reason
872 throwException dest $ PortLinkException pid reason
1161 (False, _, _) ->
873 (False, _, _) ->
1162 -- The change in sender comes from [Unified: Table 10]
874 -- The change in sender comes from [Unified: Table 10]
875+ liftIO $ sendBinary node
876+ (NodeIdentifier $ localNodeId node)
1163− ncSendToNode (processNodeId dest) $ NCMsg
877+ (NodeIdentifier $ processNodeId dest)
878+ WithImplicitReconnect
879+ NCMsg
1164− { ctrlMsgSender = NodeIdentifier (localNodeId node)
880+ { ctrlMsgSender = NodeIdentifier (localNodeId node)
1165− , ctrlMsgSignal = Died src reason
881+ , ctrlMsgSignal = Died src reason
1166− }
882+ }
1167
883
1168-- | [Unified: Table 8]
884-- | [Unified: Table 8]
1169destNid :: ProcessSignal -> Maybe NodeId
885destNid :: ProcessSignal -> Maybe NodeId
1170−destNid (Link ident) = Just $ nodeOf ident
886+destNid (Link ident) = Just $ nodeOf ident
1171−destNid (Unlink ident) = Just $ nodeOf ident
887+destNid (Unlink ident) = Just $ nodeOf ident
1172−destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
888+destNid (Monitor ref) = Just $ nodeOf (monitorRefIdent ref)
1173−destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
889+destNid (Unmonitor ref) = Just $ nodeOf (monitorRefIdent ref)
1174−destNid (Spawn _ _) = Nothing
890+destNid (Spawn _ _) = Nothing
1175−destNid (Register _ _ _ _) = Nothing
891+destNid (Register _ _ _ _) = Nothing
1176−destNid (WhereIs _) = Nothing
892+destNid (WhereIs _) = Nothing
1177−destNid (NamedSend _ _) = Nothing
893+destNid (NamedSend _ _) = Nothing
1178−destNid (UnreliableSend _ _) = Nothing
1179-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
894-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1180-- then when a local process dies the monitoring/linking machinery will take
895-- then when a local process dies the monitoring/linking machinery will take
1181-- care of notifying remote nodes
896-- care of notifying remote nodes
1182−destNid (Died _ _) = Nothing
897+destNid (Died _ _) = Nothing
1183−destNid (Kill pid _) = Just $ processNodeId pid
898+destNid (Kill pid _) = Just $ processNodeId pid
1184−destNid (Exit pid _) = Just $ processNodeId pid
899+destNid (Exit pid _) = Just $ processNodeId pid
1185−destNid (GetInfo pid) = Just $ processNodeId pid
900+destNid (GetInfo pid) = Just $ processNodeId pid
1186−destNid (GetNodeStats nid) = Just nid
1187−destNid (LocalSend pid _) = Just $ processNodeId pid
1188−destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1189−destNid (SigShutdown) = Nothing
1190
901
1191-- | Check if a process is local to our own node
902-- | Check if a process is local to our own node
1192isLocal :: LocalNode -> Identifier -> Bool
903isLocal :: LocalNode -> Identifier -> Bool
@@ -1202,7 +913,7 @@ unClosure closure = do
1202isValidLocalIdentifier :: Identifier -> NC Bool
913isValidLocalIdentifier :: Identifier -> NC Bool
1203isValidLocalIdentifier ident = do
914isValidLocalIdentifier ident = do
1204 node <- ask
915 node <- ask
1205− liftIO . withValidLocalState node $ \nSt ->
916+ liftIO . withMVar (localState node) $ \nSt ->
1206 case ident of
917 case ident of
1207 NodeIdentifier nid ->
918 NodeIdentifier nid ->
1208 return $ nid == localNodeId node
919 return $ nid == localNodeId node
@@ -1223,37 +934,34 @@ isValidLocalIdentifier ident = do
1223--------------------------------------------------------------------------------
934--------------------------------------------------------------------------------
1224
935
1225postAsMessage :: Serializable a => ProcessId -> a -> NC ()
936postAsMessage :: Serializable a => ProcessId -> a -> NC ()
1226−postAsMessage pid = postMessage pid . createUnencodedMessage
937+postAsMessage pid = postMessage pid . createMessage
1227
938
1228postMessage :: ProcessId -> Message -> NC ()
939postMessage :: ProcessId -> Message -> NC ()
1229postMessage pid msg = do
940postMessage pid msg = do
1230− node <- ask
1231− liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
941+ withLocalProc pid $ \p -> enqueue (processQueue p) msg
1232
942
1233throwException :: Exception e => ProcessId -> e -> NC ()
943throwException :: Exception e => ProcessId -> e -> NC ()
1234−throwException pid e = do
944+throwException pid e = withLocalProc pid $ \p ->
945+ throwTo (processThread p) e
946+
947+withLocalProc :: ProcessId -> (LocalProcess -> IO ()) -> NC ()
948+withLocalProc pid p = do
1235 node <- ask
949 node <- ask
1236− -- throwTo blocks until the exception is received by the target thread.
950+ liftIO $ do
1237− -- We cannot easily make it happen asynchronpusly because then 'unlink'
1238− -- semantics would break.
1239− liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1240−
1241−withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1242−withLocalProc node pid p =
1243− -- By [Unified: table 6, rule missing_process] messages to dead processes
951+ -- By [Unified: table 6, rule missing_process] messages to dead processes
1244− -- can silently be dropped
952+ -- can silently be dropped
1245− let lpid = processLocalId pid in do
953+ let lpid = processLocalId pid
1246− join $ withValidLocalState node $ \vst ->
1247− return $ forM_ (vst ^. localProcessWithId lpid) p
954+ mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
955+ forM_ mProc p
1248
956
1249--------------------------------------------------------------------------------
957--------------------------------------------------------------------------------
1250-- Accessors --
958-- Accessors --
1251--------------------------------------------------------------------------------
959--------------------------------------------------------------------------------
1252
960
1253−links :: Accessor NCState (BiMultiMap Identifier ProcessId ())
961+links :: Accessor NCState (Map Identifier (Set ProcessId))
1254links = accessor _links (\ls st -> st { _links = ls })
962links = accessor _links (\ls st -> st { _links = ls })
1255
963
1256−monitors :: Accessor NCState (BiMultiMap Identifier ProcessId MonitorRef)
964+monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1257monitors = accessor _monitors (\ms st -> st { _monitors = ms })
965monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1258
966
1259registeredHere :: Accessor NCState (Map String ProcessId)
967registeredHere :: Accessor NCState (Map String ProcessId)
@@ -1262,6 +970,12 @@ registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry }
1262registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
970registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1263registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
971registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1264
972
973+linksFor :: Identifier -> Accessor NCState (Set ProcessId)
974+linksFor ident = links >>> DAC.mapDefault Set.empty ident
975+
976+monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
977+monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
978+
1265registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
979registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1266registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
980registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1267
981
@@ -1288,12 +1002,10 @@ registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1288-- * the notifications for typed channels to that process.
1002-- * the notifications for typed channels to that process.
1289--
1003--
1290-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1004-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1291−splitNotif :: (Ord a, Ord v)
1292− => Identifier
1005+splitNotif :: Identifier
1293− -> BiMultiMap Identifier a v
1006+ -> Map Identifier a
1294− -> (Map Identifier (Set (a,v)), BiMultiMap Identifier a v)
1007+ -> (Map Identifier a, Map Identifier a)
1295−splitNotif ident =
1008+splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1296− BiMultiMap.partitionWithKeyBy1st (\k !_v -> ident `impliesDeathOf` k)
1297
1009
1298--------------------------------------------------------------------------------
1010--------------------------------------------------------------------------------
1299-- Auxiliary --
1011-- Auxiliary --
src/Control/Distributed/Process/Serializable.hs → distributed-process/src/Control/Distributed/Process/Serializable.hsrenamed+8−18
@@ -1,8 +1,3 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
2−{-# LANGUAGE UndecidableInstances #-}
3−{-# LANGUAGE FlexibleInstances #-}
4−{-# LANGUAGE ScopedTypeVariables #-}
5−{-# LANGUAGE GADTs #-}
6module Control.Distributed.Process.Serializable
1module Control.Distributed.Process.Serializable
7 ( Serializable
2 ( Serializable
8 , encodeFingerprint
3 , encodeFingerprint
@@ -12,33 +7,28 @@ 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
@@ -55,7 +45,7 @@ decodeFingerprint :: 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
@@ -65,7 +55,7 @@ sizeOfFingerprint = 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
distributed-process/tests/TestCH.hsnew file+1184
@@ -0,0 +1,1184 @@
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, myThreadId, throwTo, ThreadId)
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 (SomeException, throwIO)
20+import qualified Control.Exception as Ex (catch)
21+import Control.Applicative ((<$>), (<*>), pure, (<|>))
22+import qualified Network.Transport as NT (Transport, closeEndPoint)
23+import Network.Socket (sClose)
24+import Network.Transport.TCP
25+ ( createTransportExposeInternals
26+ , TransportInternals(socketBetween)
27+ , defaultTCPParameters
28+ )
29+import Control.Distributed.Process
30+import Control.Distributed.Process.Internal.Types
31+ ( NodeId(nodeAddress)
32+ , LocalNode(localEndPoint)
33+ , ProcessExitException(..)
34+ , nullProcessId
35+ )
36+import Control.Distributed.Process.Node
37+import Control.Distributed.Process.Serializable (Serializable)
38+
39+import Test.HUnit (Assertion)
40+import Test.Framework (Test, defaultMain, testGroup)
41+import Test.Framework.Providers.HUnit (testCase)
42+
43+newtype Ping = Ping ProcessId
44+ deriving (Typeable, Binary, Show)
45+
46+newtype Pong = Pong ProcessId
47+ deriving (Typeable, Binary, Show)
48+
49+--------------------------------------------------------------------------------
50+-- Supporting definitions --
51+--------------------------------------------------------------------------------
52+
53+-- | Like fork, but throw exceptions in the child thread to the parent
54+forkTry :: IO () -> IO ThreadId
55+forkTry p = do
56+ tid <- myThreadId
57+ forkIO $ Ex.catch p (\e -> throwTo tid (e :: SomeException))
58+
59+-- | The ping server from the paper
60+ping :: Process ()
61+ping = do
62+ Pong partner <- expect
63+ self <- getSelfPid
64+ send partner (Ping self)
65+ ping
66+
67+-- | Quick and dirty synchronous version of whereisRemoteAsync
68+whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)
69+whereisRemote nid string = do
70+ whereisRemoteAsync nid string
71+ WhereIsReply _ mPid <- expect
72+ return mPid
73+
74+data Add = Add ProcessId Double Double deriving (Typeable)
75+data Divide = Divide ProcessId Double Double deriving (Typeable)
76+data DivByZero = DivByZero deriving (Typeable)
77+
78+instance Binary Add where
79+ put (Add pid x y) = put pid >> put x >> put y
80+ get = Add <$> get <*> get <*> get
81+
82+instance Binary Divide where
83+ put (Divide pid x y) = put pid >> put x >> put y
84+ get = Divide <$> get <*> get <*> get
85+
86+instance Binary DivByZero where
87+ put DivByZero = return ()
88+ get = return DivByZero
89+
90+-- The math server from the paper
91+math :: Process ()
92+math = do
93+ receiveWait
94+ [ match (\(Add pid x y) -> send pid (x + y))
95+ , matchIf (\(Divide _ _ y) -> y /= 0)
96+ (\(Divide pid x y) -> send pid (x / y))
97+ , match (\(Divide pid _ _) -> send pid DivByZero)
98+ ]
99+ math
100+
101+-- | Monitor or link to a remote node
102+monitorOrLink :: Bool -- ^ 'True' for monitor, 'False' for link
103+ -> ProcessId -- Process to monitor/link to
104+ -> Maybe (MVar ()) -- MVar to signal on once the monitor has been set up
105+ -> Process (Maybe MonitorRef)
106+monitorOrLink mOrL pid mSignal = do
107+ result <- if mOrL then Just <$> monitor pid
108+ else link pid >> return Nothing
109+ -- Monitor is asynchronous, which usually does not matter but if we want a
110+ -- *specific* signal then it does. Therefore we wait an arbitrary delay and
111+ -- hope that this means the monitor has been set up
112+ forM_ mSignal $ \signal -> liftIO . forkIO $ threadDelay 100000 >> putMVar signal ()
113+ return result
114+
115+monitorTestProcess :: ProcessId -- Process to monitor/link to
116+ -> Bool -- 'True' for monitor, 'False' for link
117+ -> Bool -- Should we unmonitor?
118+ -> DiedReason -- Expected cause of death
119+ -> Maybe (MVar ()) -- Signal for 'monitor set up'
120+ -> MVar () -- Signal for successful termination
121+ -> Process ()
122+monitorTestProcess theirAddr mOrL un reason monitorSetup done =
123+ catch (do mRef <- monitorOrLink mOrL theirAddr monitorSetup
124+ case (un, mRef) of
125+ (True, Nothing) -> do
126+ unlink theirAddr
127+ liftIO $ putMVar done ()
128+ (True, Just ref) -> do
129+ unmonitor ref
130+ liftIO $ putMVar done ()
131+ (False, ref) -> do
132+ ProcessMonitorNotification ref' pid reason' <- expect
133+ True <- return $ Just ref' == ref && pid == theirAddr && mOrL && reason == reason'
134+ liftIO $ putMVar done ()
135+ )
136+ (\(ProcessLinkException pid reason') -> do
137+ True <- return $ pid == theirAddr && not mOrL && not un && reason == reason'
138+ liftIO $ putMVar done ()
139+ )
140+
141+--------------------------------------------------------------------------------
142+-- The tests proper --
143+--------------------------------------------------------------------------------
144+
145+-- | Basic ping test
146+testPing :: NT.Transport -> Assertion
147+testPing transport = do
148+ serverAddr <- newEmptyMVar
149+ clientDone <- newEmptyMVar
150+
151+ -- Server
152+ forkIO $ do
153+ localNode <- newLocalNode transport initRemoteTable
154+ addr <- forkProcess localNode ping
155+ putMVar serverAddr addr
156+
157+ -- Client
158+ forkIO $ do
159+ localNode <- newLocalNode transport initRemoteTable
160+ pingServer <- readMVar serverAddr
161+
162+ let numPings = 10000
163+
164+ runProcess localNode $ do
165+ pid <- getSelfPid
166+ replicateM_ numPings $ do
167+ send pingServer (Pong pid)
168+ Ping _ <- expect
169+ return ()
170+
171+ putMVar clientDone ()
172+
173+ takeMVar clientDone
174+
175+-- | Monitor a process on an unreachable node
176+testMonitorUnreachable :: NT.Transport -> Bool -> Bool -> Assertion
177+testMonitorUnreachable transport mOrL un = do
178+ deadProcess <- newEmptyMVar
179+ done <- newEmptyMVar
180+
181+ forkIO $ do
182+ localNode <- newLocalNode transport initRemoteTable
183+ addr <- forkProcess localNode . liftIO $ threadDelay 1000000
184+ closeLocalNode localNode
185+ putMVar deadProcess addr
186+
187+ forkIO $ do
188+ localNode <- newLocalNode transport initRemoteTable
189+ theirAddr <- readMVar deadProcess
190+ runProcess localNode $
191+ monitorTestProcess theirAddr mOrL un DiedDisconnect Nothing done
192+
193+ takeMVar done
194+
195+-- | Monitor a process which terminates normally
196+testMonitorNormalTermination :: NT.Transport -> Bool -> Bool -> Assertion
197+testMonitorNormalTermination transport mOrL un = do
198+ monitorSetup <- newEmptyMVar
199+ monitoredProcess <- newEmptyMVar
200+ done <- newEmptyMVar
201+
202+ forkIO $ do
203+ localNode <- newLocalNode transport initRemoteTable
204+ addr <- forkProcess localNode $
205+ liftIO $ readMVar monitorSetup
206+ putMVar monitoredProcess addr
207+
208+ forkIO $ do
209+ localNode <- newLocalNode transport initRemoteTable
210+ theirAddr <- readMVar monitoredProcess
211+ runProcess localNode $
212+ monitorTestProcess theirAddr mOrL un DiedNormal (Just monitorSetup) done
213+
214+ takeMVar done
215+
216+-- | Monitor a process which terminates abnormally
217+testMonitorAbnormalTermination :: NT.Transport -> Bool -> Bool -> Assertion
218+testMonitorAbnormalTermination transport mOrL un = do
219+ monitorSetup <- newEmptyMVar
220+ monitoredProcess <- newEmptyMVar
221+ done <- newEmptyMVar
222+
223+ let err = userError "Abnormal termination"
224+
225+ forkIO $ do
226+ localNode <- newLocalNode transport initRemoteTable
227+ addr <- forkProcess localNode . liftIO $ do
228+ readMVar monitorSetup
229+ throwIO err
230+ putMVar monitoredProcess addr
231+
232+ forkIO $ do
233+ localNode <- newLocalNode transport initRemoteTable
234+ theirAddr <- readMVar monitoredProcess
235+ runProcess localNode $
236+ monitorTestProcess theirAddr mOrL un (DiedException (show err)) (Just monitorSetup) done
237+
238+ takeMVar done
239+
240+-- | Monitor a local process that is already dead
241+testMonitorLocalDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion
242+testMonitorLocalDeadProcess transport mOrL un = do
243+ processDead <- newEmptyMVar
244+ processAddr <- newEmptyMVar
245+ localNode <- newLocalNode transport initRemoteTable
246+ done <- newEmptyMVar
247+
248+ forkIO $ do
249+ addr <- forkProcess localNode . liftIO $ putMVar processDead ()
250+ putMVar processAddr addr
251+
252+ forkIO $ do
253+ theirAddr <- readMVar processAddr
254+ readMVar processDead
255+ runProcess localNode $ do
256+ monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
257+
258+ takeMVar done
259+
260+-- | Monitor a remote process that is already dead
261+testMonitorRemoteDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion
262+testMonitorRemoteDeadProcess transport mOrL un = do
263+ processDead <- newEmptyMVar
264+ processAddr <- newEmptyMVar
265+ done <- newEmptyMVar
266+
267+ forkIO $ do
268+ localNode <- newLocalNode transport initRemoteTable
269+ addr <- forkProcess localNode . liftIO $ putMVar processDead ()
270+ putMVar processAddr addr
271+
272+ forkIO $ do
273+ localNode <- newLocalNode transport initRemoteTable
274+ theirAddr <- readMVar processAddr
275+ readMVar processDead
276+ runProcess localNode $ do
277+ monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
278+
279+ takeMVar done
280+
281+-- | Monitor a process that becomes disconnected
282+testMonitorDisconnect :: NT.Transport -> Bool -> Bool -> Assertion
283+testMonitorDisconnect transport mOrL un = do
284+ processAddr <- newEmptyMVar
285+ monitorSetup <- newEmptyMVar
286+ done <- newEmptyMVar
287+
288+ forkIO $ do
289+ localNode <- newLocalNode transport initRemoteTable
290+ addr <- forkProcess localNode . liftIO $ threadDelay 1000000
291+ putMVar processAddr addr
292+ readMVar monitorSetup
293+ NT.closeEndPoint (localEndPoint localNode)
294+
295+ forkIO $ do
296+ localNode <- newLocalNode transport initRemoteTable
297+ theirAddr <- readMVar processAddr
298+ runProcess localNode $ do
299+ monitorTestProcess theirAddr mOrL un DiedDisconnect (Just monitorSetup) done
300+
301+ takeMVar done
302+
303+-- | Test the math server (i.e., receiveWait)
304+testMath :: NT.Transport -> Assertion
305+testMath transport = do
306+ serverAddr <- newEmptyMVar
307+ clientDone <- newEmptyMVar
308+
309+ -- Server
310+ forkIO $ do
311+ localNode <- newLocalNode transport initRemoteTable
312+ addr <- forkProcess localNode math
313+ putMVar serverAddr addr
314+
315+ -- Client
316+ forkIO $ do
317+ localNode <- newLocalNode transport initRemoteTable
318+ mathServer <- readMVar serverAddr
319+
320+ runProcess localNode $ do
321+ pid <- getSelfPid
322+ send mathServer (Add pid 1 2)
323+ 3 <- expect :: Process Double
324+ send mathServer (Divide pid 8 2)
325+ 4 <- expect :: Process Double
326+ send mathServer (Divide pid 8 0)
327+ DivByZero <- expect
328+ liftIO $ putMVar clientDone ()
329+
330+ takeMVar clientDone
331+
332+-- | Send first message (i.e. connect) to an already terminated process
333+-- (without monitoring); then send another message to a second process on
334+-- the same remote node (we're checking that the remote node did not die)
335+testSendToTerminated :: NT.Transport -> Assertion
336+testSendToTerminated transport = do
337+ serverAddr1 <- newEmptyMVar
338+ serverAddr2 <- newEmptyMVar
339+ clientDone <- newEmptyMVar
340+
341+ forkIO $ do
342+ terminated <- newEmptyMVar
343+ localNode <- newLocalNode transport initRemoteTable
344+ addr1 <- forkProcess localNode $ liftIO $ putMVar terminated ()
345+ addr2 <- forkProcess localNode $ ping
346+ readMVar terminated
347+ putMVar serverAddr1 addr1
348+ putMVar serverAddr2 addr2
349+
350+ forkIO $ do
351+ localNode <- newLocalNode transport initRemoteTable
352+ server1 <- readMVar serverAddr1
353+ server2 <- readMVar serverAddr2
354+ runProcess localNode $ do
355+ pid <- getSelfPid
356+ send server1 "Hi"
357+ send server2 (Pong pid)
358+ Ping pid' <- expect
359+ True <- return $ pid' == server2
360+ liftIO $ putMVar clientDone ()
361+
362+ takeMVar clientDone
363+
364+-- | Test (non-zero) timeout
365+testTimeout :: NT.Transport -> Assertion
366+testTimeout transport = do
367+ localNode <- newLocalNode transport initRemoteTable
368+ done <- newEmptyMVar
369+
370+ runProcess localNode $ do
371+ Nothing <- receiveTimeout 1000000 [match (\(Add _ _ _) -> return ())]
372+ liftIO $ putMVar done ()
373+
374+ takeMVar done
375+
376+-- | Test zero timeout
377+testTimeout0 :: NT.Transport -> Assertion
378+testTimeout0 transport = do
379+ serverAddr <- newEmptyMVar
380+ clientDone <- newEmptyMVar
381+ messagesSent <- newEmptyMVar
382+
383+ forkIO $ do
384+ localNode <- newLocalNode transport initRemoteTable
385+ addr <- forkProcess localNode $ do
386+ liftIO $ readMVar messagesSent >> threadDelay 1000000
387+ -- Variation on the venerable ping server which uses a zero timeout
388+ -- Since we wait for all messages to be sent before doing this receive,
389+ -- we should nevertheless find the right message immediately
390+ Just partner <- receiveTimeout 0 [match (\(Pong partner) -> return partner)]
391+ self <- getSelfPid
392+ send partner (Ping self)
393+ putMVar serverAddr addr
394+
395+ forkIO $ do
396+ localNode <- newLocalNode transport initRemoteTable
397+ server <- readMVar serverAddr
398+ runProcess localNode $ do
399+ pid <- getSelfPid
400+ -- Send a bunch of messages. A large number of messages that the server
401+ -- is not interested in, and then a single message that it wants
402+ replicateM_ 10000 $ send server "Irrelevant message"
403+ send server (Pong pid)
404+ liftIO $ putMVar messagesSent ()
405+ Ping _ <- expect
406+ liftIO $ putMVar clientDone ()
407+
408+ takeMVar clientDone
409+
410+-- | Test typed channels
411+testTypedChannels :: NT.Transport -> Assertion
412+testTypedChannels transport = do
413+ serverChannel <- newEmptyMVar :: IO (MVar (SendPort (SendPort Bool, Int)))
414+ clientDone <- newEmptyMVar
415+
416+ forkIO $ do
417+ localNode <- newLocalNode transport initRemoteTable
418+ forkProcess localNode $ do
419+ (serverSendPort, rport) <- newChan
420+ liftIO $ putMVar serverChannel serverSendPort
421+ (clientSendPort, i) <- receiveChan rport
422+ sendChan clientSendPort (even i)
423+ return ()
424+
425+ forkIO $ do
426+ localNode <- newLocalNode transport initRemoteTable
427+ serverSendPort <- readMVar serverChannel
428+ runProcess localNode $ do
429+ (clientSendPort, rport) <- newChan
430+ sendChan serverSendPort (clientSendPort, 5)
431+ False <- receiveChan rport
432+ liftIO $ putMVar clientDone ()
433+
434+ takeMVar clientDone
435+
436+-- | Test merging receive ports
437+testMergeChannels :: NT.Transport -> Assertion
438+testMergeChannels transport = do
439+ localNode <- newLocalNode transport initRemoteTable
440+ testFlat localNode True "aaabbbccc"
441+ testFlat localNode False "abcabcabc"
442+ testNested localNode True True "aaabbbcccdddeeefffggghhhiii"
443+ testNested localNode True False "adgadgadgbehbehbehcficficfi"
444+ testNested localNode False True "abcabcabcdefdefdefghighighi"
445+ testNested localNode False False "adgbehcfiadgbehcfiadgbehcfi"
446+ testBlocked localNode True
447+ testBlocked localNode False
448+ where
449+ -- Single layer of merging
450+ testFlat :: LocalNode -> Bool -> String -> IO ()
451+ testFlat localNode biased expected = do
452+ done <- newEmptyMVar
453+ forkProcess localNode $ do
454+ rs <- mapM charChannel "abc"
455+ m <- mergePorts biased rs
456+ xs <- replicateM 9 $ receiveChan m
457+ True <- return $ xs == expected
458+ liftIO $ putMVar done ()
459+ takeMVar done
460+
461+ -- Two layers of merging
462+ testNested :: LocalNode -> Bool -> Bool -> String -> IO ()
463+ testNested localNode biasedInner biasedOuter expected = do
464+ done <- newEmptyMVar
465+ forkProcess localNode $ do
466+ rss <- mapM (mapM charChannel) ["abc", "def", "ghi"]
467+ ms <- mapM (mergePorts biasedInner) rss
468+ m <- mergePorts biasedOuter ms
469+ xs <- replicateM (9 * 3) $ receiveChan m
470+ True <- return $ xs == expected
471+ liftIO $ putMVar done ()
472+ takeMVar done
473+
474+ -- Test that if no messages are (immediately) available, the scheduler makes no difference
475+ testBlocked :: LocalNode -> Bool -> IO ()
476+ testBlocked localNode biased = do
477+ vs <- replicateM 3 newEmptyMVar
478+ done <- newEmptyMVar
479+
480+ forkProcess localNode $ do
481+ [sa, sb, sc] <- liftIO $ mapM readMVar vs
482+ mapM_ ((>> liftIO (threadDelay 10000)) . uncurry sendChan)
483+ [ -- a, b, c
484+ (sa, 'a')
485+ , (sb, 'b')
486+ , (sc, 'c')
487+ -- a, c, b
488+ , (sa, 'a')
489+ , (sc, 'c')
490+ , (sb, 'b')
491+ -- b, a, c
492+ , (sb, 'b')
493+ , (sa, 'a')
494+ , (sc, 'c')
495+ -- b, c, a
496+ , (sb, 'b')
497+ , (sc, 'c')
498+ , (sa, 'a')
499+ -- c, a, b
500+ , (sc, 'c')
501+ , (sa, 'a')
502+ , (sb, 'b')
503+ -- c, b, a
504+ , (sc, 'c')
505+ , (sb, 'b')
506+ , (sa, 'a')
507+ ]
508+
509+ forkProcess localNode $ do
510+ (ss, rs) <- unzip <$> replicateM 3 newChan
511+ liftIO $ mapM_ (uncurry putMVar) $ zip vs ss
512+ m <- mergePorts biased rs
513+ xs <- replicateM (6 * 3) $ receiveChan m
514+ True <- return $ xs == "abcacbbacbcacabcba"
515+ liftIO $ putMVar done ()
516+
517+ takeMVar done
518+
519+ mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)
520+ mergePorts True = mergePortsBiased
521+ mergePorts False = mergePortsRR
522+
523+ charChannel :: Char -> Process (ReceivePort Char)
524+ charChannel c = do
525+ (sport, rport) <- newChan
526+ replicateM_ 3 $ sendChan sport c
527+ liftIO $ threadDelay 10000 -- Make sure messages have been sent
528+ return rport
529+
530+testTerminate :: NT.Transport -> Assertion
531+testTerminate transport = do
532+ localNode <- newLocalNode transport initRemoteTable
533+ done <- newEmptyMVar
534+
535+ pid <- forkProcess localNode $ do
536+ liftIO $ threadDelay 100000
537+ terminate
538+
539+ runProcess localNode $ do
540+ ref <- monitor pid
541+ ProcessMonitorNotification ref' pid' (DiedException ex) <- expect
542+ True <- return $ ref == ref' && pid == pid' && ex == show ProcessTerminationException
543+ liftIO $ putMVar done ()
544+
545+ takeMVar done
546+
547+testMonitorNode :: NT.Transport -> Assertion
548+testMonitorNode transport = do
549+ [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
550+ done <- newEmptyMVar
551+
552+ closeLocalNode node1
553+
554+ runProcess node2 $ do
555+ ref <- monitorNode (localNodeId node1)
556+ NodeMonitorNotification ref' nid DiedDisconnect <- expect
557+ True <- return $ ref == ref' && nid == localNodeId node1
558+ liftIO $ putMVar done ()
559+
560+ takeMVar done
561+
562+testMonitorChannel :: NT.Transport -> Assertion
563+testMonitorChannel transport = do
564+ [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
565+ gotNotification <- newEmptyMVar
566+
567+ pid <- forkProcess node1 $ do
568+ sport <- expect :: Process (SendPort ())
569+ ref <- monitorPort sport
570+ PortMonitorNotification ref' port' reason <- expect
571+ -- reason might be DiedUnknownId if the receive port is GCed before the
572+ -- monitor is established (TODO: not sure that this is reasonable)
573+ return $ ref' == ref && port' == sendPortId sport && (reason == DiedNormal || reason == DiedUnknownId)
574+ liftIO $ putMVar gotNotification ()
575+
576+ runProcess node2 $ do
577+ (sport, _) <- newChan :: Process (SendPort (), ReceivePort ())
578+ send pid sport
579+ liftIO $ threadDelay 100000
580+
581+ takeMVar gotNotification
582+
583+testRegistry :: NT.Transport -> Assertion
584+testRegistry transport = do
585+ node <- newLocalNode transport initRemoteTable
586+ done <- newEmptyMVar
587+
588+ pingServer <- forkProcess node ping
589+
590+ runProcess node $ do
591+ register "ping" pingServer
592+ Just pid <- whereis "ping"
593+ True <- return $ pingServer == pid
594+ us <- getSelfPid
595+ nsend "ping" (Pong us)
596+ Ping pid' <- expect
597+ True <- return $ pingServer == pid'
598+ liftIO $ putMVar done ()
599+
600+ takeMVar done
601+
602+testRemoteRegistry :: NT.Transport -> Assertion
603+testRemoteRegistry transport = do
604+ node1 <- newLocalNode transport initRemoteTable
605+ node2 <- newLocalNode transport initRemoteTable
606+ done <- newEmptyMVar
607+
608+ pingServer <- forkProcess node1 ping
609+
610+ runProcess node2 $ do
611+ let nid1 = localNodeId node1
612+ registerRemoteAsync nid1 "ping" pingServer
613+ receiveWait [
614+ matchIf (\(RegisterReply label' _) -> "ping" == label')
615+ (\(RegisterReply _ _) -> return ()) ]
616+
617+ Just pid <- whereisRemote nid1 "ping"
618+ True <- return $ pingServer == pid
619+ us <- getSelfPid
620+ nsendRemote nid1 "ping" (Pong us)
621+ Ping pid' <- expect
622+ True <- return $ pingServer == pid'
623+ liftIO $ putMVar done ()
624+
625+ takeMVar done
626+
627+testSpawnLocal :: NT.Transport -> Assertion
628+testSpawnLocal transport = do
629+ node <- newLocalNode transport initRemoteTable
630+ done <- newEmptyMVar
631+
632+ runProcess node $ do
633+ us <- getSelfPid
634+
635+ pid <- spawnLocal $ do
636+ sport <- expect
637+ sendChan sport (1234 :: Int)
638+
639+ sport <- spawnChannelLocal $ \rport -> do
640+ (1234 :: Int) <- receiveChan rport
641+ send us ()
642+
643+ send pid sport
644+ () <- expect
645+ liftIO $ putMVar done ()
646+
647+ takeMVar done
648+
649+testReconnect :: NT.Transport -> TransportInternals -> Assertion
650+testReconnect transport transportInternals = do
651+ [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
652+ let nid1 = localNodeId node1
653+ nid2 = localNodeId node2
654+ processA <- newEmptyMVar
655+ [sendTestOk, registerTestOk] <- replicateM 2 newEmptyMVar
656+
657+ forkProcess node1 $ do
658+ us <- getSelfPid
659+ liftIO $ putMVar processA us
660+ msg1 <- expect
661+ msg2 <- expect
662+ True <- return $ msg1 == "message 1" && msg2 == "message 3"
663+ liftIO $ putMVar sendTestOk ()
664+
665+ forkProcess node2 $ do
666+ {-
667+ - Make sure there is no implicit reconnect on normal message sending
668+ -}
669+
670+ them <- liftIO $ readMVar processA
671+ send them "message 1" >> liftIO (threadDelay 100000)
672+
673+ -- Simulate network failure
674+ liftIO $ do
675+ sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)
676+ sClose sock
677+ threadDelay 10000
678+
679+ -- Should not arrive
680+ send them "message 2"
681+
682+ -- Should arrive
683+ reconnect them
684+ send them "message 3"
685+
686+ liftIO $ takeMVar sendTestOk
687+
688+ {-
689+ - Test that there *is* implicit reconnect on node controller messages
690+ -}
691+
692+ us <- getSelfPid
693+ registerRemoteAsync nid1 "a" us -- registerRemote is asynchronous
694+ receiveWait [
695+ matchIf (\(RegisterReply label' _) -> "a" == label')
696+ (\(RegisterReply _ _) -> return ()) ]
697+
698+ Just _ <- whereisRemote nid1 "a"
699+
700+
701+ -- Simulate network failure
702+ liftIO $ do
703+ sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)
704+ sClose sock
705+ threadDelay 10000
706+
707+ -- This will happen due to implicit reconnect
708+ registerRemoteAsync nid1 "b" us
709+ receiveWait [
710+ matchIf (\(RegisterReply label' _) -> "b" == label')
711+ (\(RegisterReply _ _) -> return ()) ]
712+
713+ -- Should happen
714+ registerRemoteAsync nid1 "c" us
715+ receiveWait [
716+ matchIf (\(RegisterReply label' _) -> "c" == label')
717+ (\(RegisterReply _ _) -> return ()) ]
718+
719+ -- Check
720+ Nothing <- whereisRemote nid1 "a" -- this will fail because the name is removed when the node is disconnected
721+ Just _ <- whereisRemote nid1 "b" -- this will suceed because the value is set after thereconnect
722+ Just _ <- whereisRemote nid1 "c"
723+
724+ liftIO $ putMVar registerTestOk ()
725+
726+ takeMVar registerTestOk
727+
728+-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
729+-- in between
730+testMatchAny :: NT.Transport -> Assertion
731+testMatchAny transport = do
732+ proxyAddr <- newEmptyMVar
733+ clientDone <- newEmptyMVar
734+
735+ -- Math server
736+ forkIO $ do
737+ localNode <- newLocalNode transport initRemoteTable
738+ mathServer <- forkProcess localNode math
739+ proxyServer <- forkProcess localNode $ forever $ do
740+ msg <- receiveWait [ matchAny return ]
741+ forward msg mathServer
742+ putMVar proxyAddr proxyServer
743+
744+ -- Client
745+ forkIO $ do
746+ localNode <- newLocalNode transport initRemoteTable
747+ mathServer <- readMVar proxyAddr
748+
749+ runProcess localNode $ do
750+ pid <- getSelfPid
751+ send mathServer (Add pid 1 2)
752+ 3 <- expect :: Process Double
753+ send mathServer (Divide pid 8 2)
754+ 4 <- expect :: Process Double
755+ send mathServer (Divide pid 8 0)
756+ DivByZero <- expect
757+ liftIO $ putMVar clientDone ()
758+
759+ takeMVar clientDone
760+
761+-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
762+-- in between, however we block 'Divide' requests ....
763+testMatchAnyHandle :: NT.Transport -> Assertion
764+testMatchAnyHandle transport = do
765+ proxyAddr <- newEmptyMVar
766+ clientDone <- newEmptyMVar
767+
768+ -- Math server
769+ forkIO $ do
770+ localNode <- newLocalNode transport initRemoteTable
771+ mathServer <- forkProcess localNode math
772+ proxyServer <- forkProcess localNode $ forever $ do
773+ receiveWait [
774+ matchAny (maybeForward mathServer)
775+ ]
776+ putMVar proxyAddr proxyServer
777+
778+ -- Client
779+ forkIO $ do
780+ localNode <- newLocalNode transport initRemoteTable
781+ mathServer <- readMVar proxyAddr
782+
783+ runProcess localNode $ do
784+ pid <- getSelfPid
785+ send mathServer (Add pid 1 2)
786+ 3 <- expect :: Process Double
787+ send mathServer (Divide pid 8 2)
788+ Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
789+ liftIO $ putMVar clientDone ()
790+
791+ takeMVar clientDone
792+ where maybeForward :: ProcessId -> AbstractMessage -> Process (Maybe ())
793+ maybeForward s msg =
794+ maybeHandleMessage msg (\m@(Add _ _ _) -> send s m)
795+
796+testMatchAnyNoHandle :: NT.Transport -> Assertion
797+testMatchAnyNoHandle transport = do
798+ addr <- newEmptyMVar
799+ clientDone <- newEmptyMVar
800+ serverDone <- newEmptyMVar
801+
802+ -- Math server
803+ forkIO $ do
804+ localNode <- newLocalNode transport initRemoteTable
805+ server <- forkProcess localNode $ forever $ do
806+ receiveWait [
807+ matchAnyIf
808+ -- the condition has type `Add -> Bool`
809+ (\(Add _ _ _) -> True)
810+ -- the match `AbstractMessage -> Process ()` will succeed!
811+ (\m -> do
812+ -- `String -> Process ()` does *not* match the input types however
813+ r <- (maybeHandleMessage m (\(_ :: String) -> die "NONSENSE" ))
814+ case r of
815+ Nothing -> return ()
816+ Just _ -> die "NONSENSE")
817+ ]
818+ -- we *must* have removed the message from our mailbox though!!!
819+ Nothing <- receiveTimeout 100000 [ match (\(Add _ _ _) -> return ()) ]
820+ liftIO $ putMVar serverDone ()
821+ putMVar addr server
822+
823+ -- Client
824+ forkIO $ do
825+ localNode <- newLocalNode transport initRemoteTable
826+ server <- readMVar addr
827+
828+ runProcess localNode $ do
829+ pid <- getSelfPid
830+ send server (Add pid 1 2)
831+ -- we only care about the client having sent a message, so we're done
832+ liftIO $ putMVar clientDone ()
833+
834+ takeMVar clientDone
835+ takeMVar serverDone
836+
837+-- | Test 'matchAnyIf'. We provide an /echo/ server, but it ignores requests
838+-- unless the text body @/= "bar"@ - this case should time out rather than
839+-- removing the message from the process mailbox.
840+testMatchAnyIf :: NT.Transport -> Assertion
841+testMatchAnyIf transport = do
842+ echoAddr <- newEmptyMVar
843+ clientDone <- newEmptyMVar
844+
845+ -- echo server
846+ forkIO $ do
847+ localNode <- newLocalNode transport initRemoteTable
848+ echoServer <- forkProcess localNode $ forever $ do
849+ receiveWait [
850+ matchAnyIf (\(_ :: ProcessId, (s :: String)) -> s /= "bar")
851+ handleMessage
852+ ]
853+ putMVar echoAddr echoServer
854+
855+ -- Client
856+ forkIO $ do
857+ localNode <- newLocalNode transport initRemoteTable
858+ server <- readMVar echoAddr
859+
860+ runProcess localNode $ do
861+ pid <- getSelfPid
862+ send server (pid, "foo")
863+ "foo" <- expect
864+ send server (pid, "baz")
865+ "baz" <- expect
866+ send server (pid, "bar")
867+ Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
868+ liftIO $ putMVar clientDone ()
869+
870+ takeMVar clientDone
871+ where handleMessage :: AbstractMessage -> Process (Maybe ())
872+ handleMessage msg =
873+ maybeHandleMessage msg (\(pid :: ProcessId, (m :: String))
874+ -> do { send pid m; return () })
875+
876+-- Test 'receiveChanTimeout'
877+testReceiveChanTimeout :: NT.Transport -> Assertion
878+testReceiveChanTimeout transport = do
879+ done <- newEmptyMVar
880+ sendPort <- newEmptyMVar
881+
882+ forkTry $ do
883+ localNode <- newLocalNode transport initRemoteTable
884+ runProcess localNode $ do
885+ -- Create a typed channel
886+ (sp, rp) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
887+ liftIO $ putMVar sendPort sp
888+
889+ -- Wait for a message with a delay. No message arrives, we should get Nothing after 1 second
890+ Nothing <- receiveChanTimeout 1000000 rp
891+
892+ -- Wait for a message with a delay again. Now a message arrives after 0.5 seconds
893+ Just True <- receiveChanTimeout 1000000 rp
894+
895+ -- Wait for a message with zero timeout: non-blocking check. No message is available, we get Nothing
896+ Nothing <- receiveChanTimeout 0 rp
897+
898+ -- Again, but now there is a message available
899+ liftIO $ threadDelay 1000000
900+ Just False <- receiveChanTimeout 0 rp
901+
902+ liftIO $ putMVar done ()
903+
904+ forkTry $ do
905+ localNode <- newLocalNode transport initRemoteTable
906+ runProcess localNode $ do
907+ sp <- liftIO $ readMVar sendPort
908+
909+ liftIO $ threadDelay 1500000
910+ sendChan sp True
911+
912+ liftIO $ threadDelay 500000
913+ sendChan sp False
914+
915+ takeMVar done
916+
917+-- | Test Functor, Applicative, Alternative and Monad instances for ReceiveChan
918+testReceiveChanFeatures :: NT.Transport -> Assertion
919+testReceiveChanFeatures transport = do
920+ done <- newEmptyMVar
921+
922+ forkTry $ do
923+ localNode <- newLocalNode transport initRemoteTable
924+ runProcess localNode $ do
925+ (spInt, rpInt) <- newChan :: Process (SendPort Int, ReceivePort Int)
926+ (spBool, rpBool) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
927+
928+ -- Test Functor instance
929+
930+ sendChan spInt 2
931+ sendChan spBool False
932+
933+ rp1 <- mergePortsBiased [even <$> rpInt, rpBool]
934+
935+ True <- receiveChan rp1
936+ False <- receiveChan rp1
937+
938+ -- Test Applicative instance
939+
940+ sendChan spInt 3
941+ sendChan spInt 4
942+
943+ let rp2 = pure (+) <*> rpInt <*> rpInt
944+
945+ 7 <- receiveChan rp2
946+
947+ -- Test Alternative instance
948+
949+ sendChan spInt 3
950+ sendChan spBool True
951+
952+ let rp3 = (even <$> rpInt) <|> rpBool
953+
954+ False <- receiveChan rp3
955+ True <- receiveChan rp3
956+
957+ -- Test Monad instance
958+
959+ sendChan spBool True
960+ sendChan spBool False
961+ sendChan spInt 5
962+
963+ let rp4 :: ReceivePort Int
964+ rp4 = do b <- rpBool
965+ if b
966+ then rpInt
967+ else return 7
968+
969+ 5 <- receiveChan rp4
970+ 7 <- receiveChan rp4
971+
972+ liftIO $ putMVar done ()
973+
974+ takeMVar done
975+
976+testKillLocal :: NT.Transport -> Assertion
977+testKillLocal transport = do
978+ localNode <- newLocalNode transport initRemoteTable
979+ done <- newEmptyMVar
980+
981+ pid <- forkProcess localNode $ do
982+ liftIO $ threadDelay 1000000
983+
984+ runProcess localNode $ do
985+ ref <- monitor pid
986+ us <- getSelfPid
987+ kill pid "TestKill"
988+ ProcessMonitorNotification ref' pid' (DiedException ex) <- expect
989+ True <- return $ ref == ref' && pid == pid' && ex == "killed-by=" ++ show us ++ ",reason=TestKill"
990+ liftIO $ putMVar done ()
991+
992+ takeMVar done
993+
994+testKillRemote :: NT.Transport -> Assertion
995+testKillRemote transport = do
996+ node1 <- newLocalNode transport initRemoteTable
997+ node2 <- newLocalNode transport initRemoteTable
998+ done <- newEmptyMVar
999+
1000+ pid <- forkProcess node1 $ do
1001+ liftIO $ threadDelay 1000000
1002+
1003+ runProcess node2 $ do
1004+ ref <- monitor pid
1005+ us <- getSelfPid
1006+ kill pid "TestKill"
1007+ ProcessMonitorNotification ref' pid' (DiedException reason) <- expect
1008+ True <- return $ ref == ref' && pid == pid' && reason == "killed-by=" ++ show us ++ ",reason=TestKill"
1009+ liftIO $ putMVar done ()
1010+
1011+ takeMVar done
1012+
1013+testCatchesExit :: NT.Transport -> Assertion
1014+testCatchesExit transport = do
1015+ localNode <- newLocalNode transport initRemoteTable
1016+ done <- newEmptyMVar
1017+
1018+ _ <- forkProcess localNode $ do
1019+ (die ("foobar", 123 :: Int))
1020+ `catchesExit` [
1021+ (\_ m -> maybeHandleMessage m (\(_ :: String) -> return ()))
1022+ , (\_ m -> maybeHandleMessage m (\(_ :: Maybe Int) -> return ()))
1023+ , (\_ m -> maybeHandleMessage m (\(_ :: String, _ :: Int)
1024+ -> (liftIO $ putMVar done ()) >> return ()))
1025+ ]
1026+
1027+ takeMVar done
1028+
1029+testCatches :: NT.Transport -> Assertion
1030+testCatches transport = do
1031+ localNode <- newLocalNode transport initRemoteTable
1032+ done <- newEmptyMVar
1033+
1034+ _ <- forkProcess localNode $ do
1035+ node <- getSelfNode
1036+ (liftIO $ throwIO (ProcessLinkException (nullProcessId node) DiedNormal))
1037+ `catches` [
1038+ Handler (\(ProcessLinkException _ _) -> liftIO $ putMVar done ())
1039+ ]
1040+
1041+ takeMVar done
1042+
1043+testDie :: NT.Transport -> Assertion
1044+testDie transport = do
1045+ localNode <- newLocalNode transport initRemoteTable
1046+ done <- newEmptyMVar
1047+
1048+ _ <- forkProcess localNode $ do
1049+ (die ("foobar", 123 :: Int))
1050+ `catchExit` \_from reason -> do
1051+ -- TODO: should verify that 'from' has the right value
1052+ True <- return $ reason == ("foobar", 123 :: Int)
1053+ liftIO $ putMVar done ()
1054+
1055+ takeMVar done
1056+
1057+testPrettyExit :: NT.Transport -> Assertion
1058+testPrettyExit transport = do
1059+ localNode <- newLocalNode transport initRemoteTable
1060+ done <- newEmptyMVar
1061+
1062+ _ <- forkProcess localNode $ do
1063+ (die "timeout")
1064+ `catch` \ex@(ProcessExitException from _) ->
1065+ let expected = "exit-from=" ++ (show from)
1066+ in do
1067+ True <- return $ (show ex) == expected
1068+ liftIO $ putMVar done ()
1069+
1070+ takeMVar done
1071+
1072+testExitLocal :: NT.Transport -> Assertion
1073+testExitLocal transport = do
1074+ localNode <- newLocalNode transport initRemoteTable
1075+ supervisedDone <- newEmptyMVar
1076+ supervisorDone <- newEmptyMVar
1077+
1078+ pid <- forkProcess localNode $ do
1079+ (liftIO $ threadDelay 100000)
1080+ `catchExit` \_from reason -> do
1081+ -- TODO: should verify that 'from' has the right value
1082+ True <- return $ reason == "TestExit"
1083+ liftIO $ putMVar supervisedDone ()
1084+
1085+ runProcess localNode $ do
1086+ ref <- monitor pid
1087+ exit pid "TestExit"
1088+ -- This time the client catches the exception, so it dies normally
1089+ ProcessMonitorNotification ref' pid' DiedNormal <- expect
1090+ True <- return $ ref == ref' && pid == pid'
1091+ liftIO $ putMVar supervisorDone ()
1092+
1093+ takeMVar supervisedDone
1094+ takeMVar supervisorDone
1095+
1096+testExitRemote :: NT.Transport -> Assertion
1097+testExitRemote transport = do
1098+ node1 <- newLocalNode transport initRemoteTable
1099+ node2 <- newLocalNode transport initRemoteTable
1100+ supervisedDone <- newEmptyMVar
1101+ supervisorDone <- newEmptyMVar
1102+
1103+ pid <- forkProcess node1 $ do
1104+ (liftIO $ threadDelay 100000)
1105+ `catchExit` \_from reason -> do
1106+ -- TODO: should verify that 'from' has the right value
1107+ True <- return $ reason == "TestExit"
1108+ liftIO $ putMVar supervisedDone ()
1109+
1110+ runProcess node2 $ do
1111+ ref <- monitor pid
1112+ exit pid "TestExit"
1113+ ProcessMonitorNotification ref' pid' DiedNormal <- expect
1114+ True <- return $ ref == ref' && pid == pid'
1115+ liftIO $ putMVar supervisorDone ()
1116+
1117+ takeMVar supervisedDone
1118+ takeMVar supervisorDone
1119+
1120+tests :: (NT.Transport, TransportInternals) -> [Test]
1121+tests (transport, transportInternals) = [
1122+ testGroup "Basic features" [
1123+ testCase "Ping" (testPing transport)
1124+ , testCase "Math" (testMath transport)
1125+ , testCase "Timeout" (testTimeout transport)
1126+ , testCase "Timeout0" (testTimeout0 transport)
1127+ , testCase "SendToTerminated" (testSendToTerminated transport)
1128+ , testCase "TypedChannnels" (testTypedChannels transport)
1129+ , testCase "MergeChannels" (testMergeChannels transport)
1130+ , testCase "Terminate" (testTerminate transport)
1131+ , testCase "Registry" (testRegistry transport)
1132+ , testCase "RemoteRegistry" (testRemoteRegistry transport)
1133+ , testCase "SpawnLocal" (testSpawnLocal transport)
1134+ , testCase "MatchAny" (testMatchAny transport)
1135+ , testCase "MatchAnyHandle" (testMatchAnyHandle transport)
1136+ , testCase "MatchAnyNoHandle" (testMatchAnyNoHandle transport)
1137+ , testCase "MatchAnyIf" (testMatchAnyIf transport)
1138+ , testCase "ReceiveChanTimeout" (testReceiveChanTimeout transport)
1139+ , testCase "ReceiveChanFeatures" (testReceiveChanFeatures transport)
1140+ , testCase "KillLocal" (testKillLocal transport)
1141+ , testCase "KillRemote" (testKillRemote transport)
1142+ , testCase "Die" (testDie transport)
1143+ , testCase "PrettyExit" (testPrettyExit transport)
1144+ , testCase "CatchesExit" (testCatchesExit transport)
1145+ , testCase "Catches" (testCatches transport)
1146+ , testCase "ExitLocal" (testExitLocal transport)
1147+ , testCase "ExitRemote" (testExitRemote transport)
1148+ ]
1149+ , testGroup "Monitoring and Linking" [
1150+ -- Monitoring processes
1151+ --
1152+ -- The "missing" combinations in the list below don't make much sense, as
1153+ -- we cannot guarantee that the monitor reply or link exception will not
1154+ -- happen before the unmonitor or unlink
1155+ testCase "MonitorUnreachable" (testMonitorUnreachable transport True False)
1156+ , testCase "MonitorNormalTermination" (testMonitorNormalTermination transport True False)
1157+ , testCase "MonitorAbnormalTermination" (testMonitorAbnormalTermination transport True False)
1158+ , testCase "MonitorLocalDeadProcess" (testMonitorLocalDeadProcess transport True False)
1159+ , testCase "MonitorRemoteDeadProcess" (testMonitorRemoteDeadProcess transport True False)
1160+ , testCase "MonitorDisconnect" (testMonitorDisconnect transport True False)
1161+ , testCase "LinkUnreachable" (testMonitorUnreachable transport False False)
1162+ , testCase "LinkNormalTermination" (testMonitorNormalTermination transport False False)
1163+ , testCase "LinkAbnormalTermination" (testMonitorAbnormalTermination transport False False)
1164+ , testCase "LinkLocalDeadProcess" (testMonitorLocalDeadProcess transport False False)
1165+ , testCase "LinkRemoteDeadProcess" (testMonitorRemoteDeadProcess transport False False)
1166+ , testCase "LinkDisconnect" (testMonitorDisconnect transport False False)
1167+ , testCase "UnmonitorNormalTermination" (testMonitorNormalTermination transport True True)
1168+ , testCase "UnmonitorAbnormalTermination" (testMonitorAbnormalTermination transport True True)
1169+ , testCase "UnmonitorDisconnect" (testMonitorDisconnect transport True True)
1170+ , testCase "UnlinkNormalTermination" (testMonitorNormalTermination transport False True)
1171+ , testCase "UnlinkAbnormalTermination" (testMonitorAbnormalTermination transport False True)
1172+ , testCase "UnlinkDisconnect" (testMonitorDisconnect transport False True)
1173+ -- Monitoring nodes and channels
1174+ , testCase "MonitorNode" (testMonitorNode transport)
1175+ , testCase "MonitorChannel" (testMonitorChannel transport)
1176+ -- Reconnect
1177+ , testCase "Reconnect" (testReconnect transport transportInternals)
1178+ ]
1179+ ]
1180+
1181+main :: IO ()
1182+main = do
1183+ Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
1184+ defaultMain (tests transport)
distributed-process/tests/TestClosure.hsnew file+480
@@ -0,0 +1,480 @@
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, replicateM_, void)
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+
33+import Test.HUnit (Assertion)
34+import Test.Framework (Test, defaultMain)
35+import Test.Framework.Providers.HUnit (testCase)
36+
37+--------------------------------------------------------------------------------
38+-- Supporting definitions --
39+--------------------------------------------------------------------------------
40+
41+quintuple :: a -> b -> c -> d -> e -> (a, b, c, d, e)
42+quintuple a b c d e = (a, b, c, d, e)
43+
44+sdictInt :: SerializableDict Int
45+sdictInt = SerializableDict
46+
47+factorial :: Int -> Process Int
48+factorial 0 = return 1
49+factorial n = (n *) <$> factorial (n - 1)
50+
51+addInt :: Int -> Int -> Int
52+addInt x y = x + y
53+
54+putInt :: Int -> MVar Int -> IO ()
55+putInt = flip putMVar
56+
57+sendPid :: ProcessId -> Process ()
58+sendPid toPid = do
59+ fromPid <- getSelfPid
60+ send toPid fromPid
61+
62+wait :: Int -> Process ()
63+wait = liftIO . threadDelay
64+
65+isPrime :: Integer -> Process Bool
66+isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..]
67+ where
68+ sieve :: [Integer] -> [Integer]
69+ sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]
70+ sieve [] = error "Uh oh -- we've run out of primes"
71+
72+-- | First argument indicates empty closure environment
73+typedPingServer :: () -> ReceivePort (SendPort ()) -> Process ()
74+typedPingServer () rport = forever $ do
75+ sport <- receiveChan rport
76+ sendChan sport ()
77+
78+signal :: ProcessId -> Process ()
79+signal pid = send pid ()
80+
81+remotable [ 'factorial
82+ , 'addInt
83+ , 'putInt
84+ , 'sendPid
85+ , 'sdictInt
86+ , 'wait
87+ , 'typedPingServer
88+ , 'isPrime
89+ , 'quintuple
90+ , 'signal
91+ ]
92+
93+randomElement :: [a] -> IO a
94+randomElement xs = do
95+ ix <- randomIO
96+ return (xs !! (ix `mod` length xs))
97+
98+remotableDecl [
99+ [d| dfib :: ([NodeId], SendPort Integer, Integer) -> Process () ;
100+ dfib (_, reply, 0) = sendChan reply 0
101+ dfib (_, reply, 1) = sendChan reply 1
102+ dfib (nids, reply, n) = do
103+ nid1 <- liftIO $ randomElement nids
104+ nid2 <- liftIO $ randomElement nids
105+ (sport, rport) <- newChan
106+ spawn nid1 $ $(mkClosure 'dfib) (nids, sport, n - 2)
107+ spawn nid2 $ $(mkClosure 'dfib) (nids, sport, n - 1)
108+ n1 <- receiveChan rport
109+ n2 <- receiveChan rport
110+ sendChan reply $ n1 + n2
111+ |]
112+ ]
113+
114+-- Just try creating a static polymorphic value
115+staticQuintuple :: (Typeable a, Typeable b, Typeable c, Typeable d, Typeable e)
116+ => Static (a -> b -> c -> d -> e -> (a, b, c, d, e))
117+staticQuintuple = $(mkStatic 'quintuple)
118+
119+factorialClosure :: Int -> Closure (Process Int)
120+factorialClosure = $(mkClosure 'factorial)
121+
122+addIntClosure :: Int -> Closure (Int -> Int)
123+addIntClosure = $(mkClosure 'addInt)
124+
125+putIntClosure :: Int -> Closure (MVar Int -> IO ())
126+putIntClosure = $(mkClosure 'putInt)
127+
128+sendPidClosure :: ProcessId -> Closure (Process ())
129+sendPidClosure = $(mkClosure 'sendPid)
130+
131+sendFac :: Int -> ProcessId -> Closure (Process ())
132+sendFac n pid = factorialClosure n `bindCP` cpSend $(mkStatic 'sdictInt) pid
133+
134+factorialOf :: Closure (Int -> Process Int)
135+factorialOf = staticClosure $(mkStatic 'factorial)
136+
137+factorial' :: Int -> Closure (Process Int)
138+factorial' n = returnCP $(mkStatic 'sdictInt) n `bindCP` factorialOf
139+
140+waitClosure :: Int -> Closure (Process ())
141+waitClosure = $(mkClosure 'wait)
142+
143+simulateNetworkFailure :: TransportInternals -> NodeId -> NodeId -> Process ()
144+simulateNetworkFailure transportInternals fr to = liftIO $ do
145+ threadDelay 10000
146+ sock <- socketBetween transportInternals (nodeAddress fr) (nodeAddress to)
147+ sClose sock
148+ threadDelay 10000
149+
150+--------------------------------------------------------------------------------
151+-- The tests proper --
152+--------------------------------------------------------------------------------
153+
154+testUnclosure :: Transport -> RemoteTable -> Assertion
155+testUnclosure transport rtable = do
156+ node <- newLocalNode transport rtable
157+ done <- newEmptyMVar
158+ forkProcess node $ do
159+ 120 <- join . unClosure $ factorialClosure 5
160+ liftIO $ putMVar done ()
161+ takeMVar done
162+
163+testBind :: Transport -> RemoteTable -> Assertion
164+testBind transport rtable = do
165+ node <- newLocalNode transport rtable
166+ done <- newEmptyMVar
167+ runProcess node $ do
168+ us <- getSelfPid
169+ join . unClosure $ sendFac 6 us
170+ (720 :: Int) <- expect
171+ liftIO $ putMVar done ()
172+ takeMVar done
173+
174+testSendPureClosure :: Transport -> RemoteTable -> Assertion
175+testSendPureClosure transport rtable = do
176+ serverAddr <- newEmptyMVar
177+ serverDone <- newEmptyMVar
178+
179+ forkIO $ do
180+ node <- newLocalNode transport rtable
181+ addr <- forkProcess node $ do
182+ cl <- expect
183+ fn <- unClosure cl :: Process (Int -> Int)
184+ 13 <- return $ fn 6
185+ liftIO $ putMVar serverDone ()
186+ putMVar serverAddr addr
187+
188+ forkIO $ do
189+ node <- newLocalNode transport rtable
190+ theirAddr <- readMVar serverAddr
191+ runProcess node $ send theirAddr (addIntClosure 7)
192+
193+ takeMVar serverDone
194+
195+testSendIOClosure :: Transport -> RemoteTable -> Assertion
196+testSendIOClosure transport rtable = do
197+ serverAddr <- newEmptyMVar
198+ serverDone <- newEmptyMVar
199+
200+ forkIO $ do
201+ node <- newLocalNode transport rtable
202+ addr <- forkProcess node $ do
203+ cl <- expect
204+ io <- unClosure cl :: Process (MVar Int -> IO ())
205+ liftIO $ do
206+ someMVar <- newEmptyMVar
207+ io someMVar
208+ 5 <- readMVar someMVar
209+ putMVar serverDone ()
210+ putMVar serverAddr addr
211+
212+ forkIO $ do
213+ node <- newLocalNode transport rtable
214+ theirAddr <- readMVar serverAddr
215+ runProcess node $ send theirAddr (putIntClosure 5)
216+
217+ takeMVar serverDone
218+
219+testSendProcClosure :: Transport -> RemoteTable -> Assertion
220+testSendProcClosure transport rtable = do
221+ serverAddr <- newEmptyMVar
222+ clientDone <- newEmptyMVar
223+
224+ forkIO $ do
225+ node <- newLocalNode transport rtable
226+ addr <- forkProcess node $ do
227+ cl <- expect
228+ pr <- unClosure cl :: Process (Int -> Process ())
229+ pr 5
230+ putMVar serverAddr addr
231+
232+ forkIO $ do
233+ node <- newLocalNode transport rtable
234+ theirAddr <- readMVar serverAddr
235+ runProcess node $ do
236+ pid <- getSelfPid
237+ send theirAddr (cpSend $(mkStatic 'sdictInt) pid)
238+ 5 <- expect :: Process Int
239+ liftIO $ putMVar clientDone ()
240+
241+ takeMVar clientDone
242+
243+testSpawn :: Transport -> RemoteTable -> Assertion
244+testSpawn transport rtable = do
245+ serverNodeAddr <- newEmptyMVar
246+ clientDone <- newEmptyMVar
247+
248+ forkIO $ do
249+ node <- newLocalNode transport rtable
250+ putMVar serverNodeAddr (localNodeId node)
251+
252+ forkIO $ do
253+ node <- newLocalNode transport rtable
254+ nid <- readMVar serverNodeAddr
255+ runProcess node $ do
256+ pid <- getSelfPid
257+ pid' <- spawn nid (sendPidClosure pid)
258+ pid'' <- expect
259+ True <- return $ pid' == pid''
260+ liftIO $ putMVar clientDone ()
261+
262+ takeMVar clientDone
263+
264+testCall :: Transport -> RemoteTable -> Assertion
265+testCall 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 (factorialClosure 5)
278+ liftIO $ putMVar clientDone ()
279+
280+ takeMVar clientDone
281+
282+testCallBind :: Transport -> RemoteTable -> Assertion
283+testCallBind transport rtable = do
284+ serverNodeAddr <- newEmptyMVar
285+ clientDone <- newEmptyMVar
286+
287+ forkIO $ do
288+ node <- newLocalNode transport rtable
289+ putMVar serverNodeAddr (localNodeId node)
290+
291+ forkIO $ do
292+ node <- newLocalNode transport rtable
293+ nid <- readMVar serverNodeAddr
294+ runProcess node $ do
295+ (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorial' 5)
296+ liftIO $ putMVar clientDone ()
297+
298+ takeMVar clientDone
299+
300+testSeq :: Transport -> RemoteTable -> Assertion
301+testSeq transport rtable = do
302+ node <- newLocalNode transport rtable
303+ done <- newEmptyMVar
304+ runProcess node $ do
305+ us <- getSelfPid
306+ join . unClosure $ sendFac 5 us `seqCP` sendFac 6 us
307+ 120 :: Int <- expect
308+ 720 :: Int <- expect
309+ liftIO $ putMVar done ()
310+ takeMVar done
311+
312+-- Test 'spawnSupervised'
313+--
314+-- Set up a supervisor, spawn a child, then have a third process monitor the
315+-- child. The supervisor then throws an exception, the child dies because it
316+-- was linked to the supervisor, and the third process notices that the child
317+-- dies.
318+testSpawnSupervised :: Transport -> RemoteTable -> Assertion
319+testSpawnSupervised transport rtable = do
320+ [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
321+ [superPid, childPid] <- replicateM 2 $ newEmptyMVar
322+ thirdProcessDone <- newEmptyMVar
323+
324+ forkProcess node1 $ do
325+ us <- getSelfPid
326+ liftIO $ putMVar superPid us
327+ (child, _ref) <- spawnSupervised (localNodeId node2) (waitClosure 1000000)
328+ liftIO $ do
329+ putMVar childPid child
330+ threadDelay 500000 -- Give the child a chance to link to us
331+ throw supervisorDeath
332+
333+ forkProcess node2 $ do
334+ [super, child] <- liftIO $ mapM readMVar [superPid, childPid]
335+ ref <- monitor child
336+ ProcessMonitorNotification ref' pid' (DiedException e) <- expect
337+ True <- return $ ref' == ref
338+ && pid' == child
339+ && e == show (ProcessLinkException super (DiedException (show supervisorDeath)))
340+ liftIO $ putMVar thirdProcessDone ()
341+
342+ takeMVar thirdProcessDone
343+ where
344+ supervisorDeath :: IOException
345+ supervisorDeath = userError "Supervisor died"
346+
347+testSpawnInvalid :: Transport -> RemoteTable -> Assertion
348+testSpawnInvalid transport rtable = do
349+ node <- newLocalNode transport rtable
350+ done <- newEmptyMVar
351+ forkProcess node $ do
352+ (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)
353+ ProcessMonitorNotification ref' pid' _reason <- expect
354+ -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure
355+ True <- return $ ref' == ref && pid == pid'
356+ liftIO $ putMVar done ()
357+ takeMVar done
358+
359+testClosureExpect :: Transport -> RemoteTable -> Assertion
360+testClosureExpect transport rtable = do
361+ node <- newLocalNode transport rtable
362+ done <- newEmptyMVar
363+ runProcess node $ do
364+ nodeId <- getSelfNode
365+ us <- getSelfPid
366+ them <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us
367+ send them (1234 :: Int)
368+ (1234 :: Int) <- expect
369+ liftIO $ putMVar done ()
370+ takeMVar done
371+
372+testSpawnChannel :: Transport -> RemoteTable -> Assertion
373+testSpawnChannel transport rtable = do
374+ done <- newEmptyMVar
375+ [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
376+
377+ forkProcess node1 $ do
378+ pingServer <- spawnChannel
379+ (sdictSendPort sdictUnit)
380+ (localNodeId node2)
381+ ($(mkClosure 'typedPingServer) ())
382+ (sendReply, receiveReply) <- newChan
383+ sendChan pingServer sendReply
384+ receiveChan receiveReply
385+ liftIO $ putMVar done ()
386+
387+ takeMVar done
388+
389+testTDict :: Transport -> RemoteTable -> Assertion
390+testTDict transport rtable = do
391+ done <- newEmptyMVar
392+ [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
393+ forkProcess node1 $ do
394+ True <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))
395+ liftIO $ putMVar done ()
396+ takeMVar done
397+
398+testFib :: Transport -> RemoteTable -> Assertion
399+testFib transport rtable = do
400+ nodes <- replicateM 4 $ newLocalNode transport rtable
401+ done <- newEmptyMVar
402+
403+ forkProcess (head nodes) $ do
404+ (sport, rport) <- newChan
405+ spawnLocal $ dfib (map localNodeId nodes, sport, 10)
406+ 55 <- receiveChan rport :: Process Integer
407+ liftIO $ putMVar done ()
408+
409+ takeMVar done
410+
411+testSpawnReconnect :: Transport -> RemoteTable -> TransportInternals -> Assertion
412+testSpawnReconnect transport rtable transportInternals = do
413+ [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
414+ let nid1 = localNodeId node1
415+ nid2 = localNodeId node2
416+ done <- newEmptyMVar
417+ iv <- newMVar (0 :: Int)
418+
419+ incr <- forkProcess node1 $ forever $ do
420+ () <- expect
421+ liftIO $ modifyMVar_ iv (return . (+ 1))
422+
423+ forkProcess node2 $ do
424+ _pid1 <- spawn nid1 ($(mkClosure 'signal) incr)
425+ simulateNetworkFailure transportInternals nid2 nid1
426+ _pid2 <- spawn nid1 ($(mkClosure 'signal) incr)
427+ _pid3 <- spawn nid1 ($(mkClosure 'signal) incr)
428+
429+ liftIO $ threadDelay 100000
430+
431+ count <- liftIO $ takeMVar iv
432+ True <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'
433+
434+ liftIO $ putMVar done ()
435+
436+ takeMVar done
437+
438+-- | 'spawn' used to ave a race condition which would be triggered if the
439+-- spawning process terminates immediately after spawning
440+testSpawnTerminate :: Transport -> RemoteTable -> Assertion
441+testSpawnTerminate transport rtable = do
442+ slave <- newLocalNode transport rtable
443+ master <- newLocalNode transport rtable
444+ masterDone <- newEmptyMVar
445+
446+ runProcess master $ do
447+ us <- getSelfPid
448+ replicateM_ 1000 . spawnLocal . void . spawn (localNodeId slave) $ $(mkClosure 'signal) us
449+ replicateM_ 1000 $ (expect :: Process ())
450+ liftIO $ putMVar masterDone ()
451+
452+ takeMVar masterDone
453+
454+tests :: (Transport, TransportInternals) -> RemoteTable -> [Test]
455+tests (transport, transportInternals) rtable = [
456+ testCase "Unclosure" (testUnclosure transport rtable)
457+ , testCase "Bind" (testBind transport rtable)
458+ , testCase "SendPureClosure" (testSendPureClosure transport rtable)
459+ , testCase "SendIOClosure" (testSendIOClosure transport rtable)
460+ , testCase "SendProcClosure" (testSendProcClosure transport rtable)
461+ , testCase "Spawn" (testSpawn transport rtable)
462+ , testCase "Call" (testCall transport rtable)
463+ , testCase "CallBind" (testCallBind transport rtable)
464+ , testCase "Seq" (testSeq transport rtable)
465+ , testCase "SpawnSupervised" (testSpawnSupervised transport rtable)
466+ , testCase "SpawnInvalid" (testSpawnInvalid transport rtable)
467+ , testCase "ClosureExpect" (testClosureExpect transport rtable)
468+ , testCase "SpawnChannel" (testSpawnChannel transport rtable)
469+ , testCase "TDict" (testTDict transport rtable)
470+ , testCase "Fib" (testFib transport rtable)
471+ , testCase "SpawnTerminate" (testSpawnTerminate transport rtable)
472+ , testCase "SpawnReconnect" (testSpawnReconnect transport rtable transportInternals)
473+ ]
474+
475+
476+main :: IO ()
477+main = do
478+ Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
479+ let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable
480+ defaultMain (tests transport rtable)
distributed-process-tests/src/Control/Distributed/Process/Tests/Receive.hs → distributed-process/tests/TestReceive.hsrenamed+36−22
@@ -1,21 +1,24 @@
1−{-# LANGUAGE TemplateHaskell #-}
1+{-# LANGUAGE TemplateHaskell, DeriveDataTypeable #-}
2{-# OPTIONS_GHC -Wall #-}
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)
12import Control.Distributed.Process
3import Control.Distributed.Process
4+import Control.Distributed.Process.Closure
13import Control.Distributed.Process.Node
5import Control.Distributed.Process.Node
14
6
7+import Network.Transport (Transport)
8+import Network.Transport.TCP
9+ ( createTransportExposeInternals
10+ , defaultTCPParameters
11+ , TransportInternals(socketBetween)
12+ )
13+
15import Control.Monad
14import Control.Monad
15+import Text.Printf
16+import Data.DeriveTH
17+import Data.Binary
18+import Data.Typeable
16
19
17import Test.HUnit (Assertion, (@?=))
20import Test.HUnit (Assertion, (@?=))
18−import Test.Framework (Test)
21+import Test.Framework (Test, defaultMain)
19import Test.Framework.Providers.HUnit (testCase)
22import Test.Framework.Providers.HUnit (testCase)
20
23
21-- Tests:
24-- Tests:
@@ -42,7 +45,7 @@ recTest2 :: ReceivePort ()
42 -> SendPort String
45 -> SendPort String
43 -> ReceivePort String -> ReceivePort String
46 -> ReceivePort String -> ReceivePort String
44 -> Process ()
47 -> Process ()
45−recTest2 wait sync r1 _ = do
48+recTest2 wait sync r1 r2 = do
46 forever $ do
49 forever $ do
47 receiveChan wait
50 receiveChan wait
48 r <- receiveWait
51 r <- receiveWait
@@ -55,7 +58,7 @@ recTest3 :: ReceivePort ()
55 -> SendPort String
58 -> SendPort String
56 -> ReceivePort String -> ReceivePort String
59 -> ReceivePort String -> ReceivePort String
57 -> Process ()
60 -> Process ()
58−recTest3 wait sync r1 _ = do
61+recTest3 wait sync r1 r2 = do
59 forever $ do
62 forever $ do
60 receiveChan wait
63 receiveChan wait
61 r <- receiveWait
64 r <- receiveWait
@@ -68,7 +71,7 @@ recTest4 :: ReceivePort ()
68 -> SendPort String
71 -> SendPort String
69 -> ReceivePort String -> ReceivePort String
72 -> ReceivePort String -> ReceivePort String
70 -> Process ()
73 -> Process ()
71−recTest4 wait sync r1 _ = do
74+recTest4 wait sync r1 r2 = do
72 forever $ do
75 forever $ do
73 receiveChan wait
76 receiveChan wait
74 r <- receiveWait
77 r <- receiveWait
@@ -78,15 +81,17 @@ recTest4 wait sync r1 _ = do
78 ]
81 ]
79 sendChan sync r
82 sendChan sync r
80
83
84+
81master :: Process ()
85master :: Process ()
82master = do
86master = do
87+
83 (waits,waitr) <- newChan
88 (waits,waitr) <- newChan
84 (syncs,syncr) <- newChan
89 (syncs,syncr) <- newChan
85− let go expected = do
90+ let go expect = do
86 sendChan waits ()
91 sendChan waits ()
87 r <- receiveChan syncr
92 r <- receiveChan syncr
88− liftIO $ print (r, expected, r == expected)
93+ liftIO $ print (r,expect, r == expect)
89− liftIO $ r @?= expected
94+ liftIO $ r @?= expect
90
95
91 liftIO $ putStrLn "---- Test 1 ----"
96 liftIO $ putStrLn "---- Test 1 ----"
92 (s1,r1) <- newChan
97 (s1,r1) <- newChan
@@ -142,13 +147,22 @@ master = do
142
147
143 terminate
148 terminate
144
149
150+
145testReceive :: Transport -> RemoteTable -> Assertion
151testReceive :: Transport -> RemoteTable -> Assertion
146testReceive transport rtable = do
152testReceive transport rtable = do
147 node <- newLocalNode transport rtable
153 node <- newLocalNode transport rtable
148 runProcess node $ master
154 runProcess node $ master
149
155
150−tests :: TestTransport -> IO [Test]
156+tests :: (Transport, TransportInternals) -> RemoteTable -> [Test]
151−tests TestTransport{..} = do
157+tests (transport, transportInternals) rtable = [
158+ testCase "testReceive" (testReceive transport rtable)
159+ ]
160+
161+-- NB. test doesn't work, because failure exceptions don't get
162+-- propagated. The test always claims to succeed, even if it failed.
163+
164+main :: IO ()
165+main = do
166+ Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
152− let rtable = initRemoteTable
167+ let rtable = initRemoteTable
153− return
168+ defaultMain (tests transport rtable)
154− [ testCase "testReceive" (testReceive testTransport rtable) ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Stats.hs → distributed-process/tests/TestStats.hsrenamed+64−31
@@ -1,9 +1,6 @@
1{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2{-# OPTIONS_GHC -fno-warn-orphans #-}
2{-# OPTIONS_GHC -fno-warn-orphans #-}
3−module Control.Distributed.Process.Tests.Stats (tests) where
3+module Main where
4−
5−import Control.Distributed.Process.Tests.Internal.Utils
6−import Network.Transport.Test (TestTransport(..))
7
4
8import Control.Concurrent.MVar
5import Control.Concurrent.MVar
9 ( MVar
6 ( MVar
@@ -13,23 +10,50 @@ import Control.Concurrent.MVar
13 )
10 )
14import Control.Distributed.Process
11import Control.Distributed.Process
15import Control.Distributed.Process.Node
12import Control.Distributed.Process.Node
13+ ( forkProcess
14+ , newLocalNode
15+ , initRemoteTable
16+ , closeLocalNode
17+ , LocalNode)
16−import Data.Binary ()
18+import Data.Binary()
17−import Data.Typeable ()
19+import Data.Typeable()
18−
20+import Network.Transport.TCP
19−#if ! MIN_VERSION_base(4,6,0)
20−import Prelude hiding (catch)
21+import Prelude hiding (catch, log)
21−#endif
22−
23import Test.Framework
22import Test.Framework
24 ( Test
23 ( Test
24+ , defaultMain
25 , testGroup
25 , testGroup
26 )
26 )
27import Test.HUnit (Assertion)
27import Test.HUnit (Assertion)
28+import Test.HUnit.Base (assertBool)
28import Test.Framework.Providers.HUnit (testCase)
29import Test.Framework.Providers.HUnit (testCase)
29
30
31+-- these utilities have been cribbed from distributed-process-platform
32+-- we should really find a way to share them...
33+
34+-- | A mutable cell containing a test result.
35+type TestResult a = MVar a
36+
37+delayedAssertion :: (Eq a) => String -> LocalNode -> a ->
38+ (TestResult a -> Process ()) -> Assertion
39+delayedAssertion note localNode expected testProc = do
40+ result <- newEmptyMVar
41+ _ <- forkProcess localNode $ testProc result
42+ assertComplete note result expected
43+
44+assertComplete :: (Eq a) => String -> MVar a -> a -> IO ()
45+assertComplete msg mv a = do
46+ b <- takeMVar mv
47+ assertBool msg (a == b)
48+
49+stash :: TestResult a -> a -> Process ()
50+stash mvar x = liftIO $ putMVar mvar x
51+
52+------
53+
30testLocalDeadProcessInfo :: TestResult (Maybe ProcessInfo) -> Process ()
54testLocalDeadProcessInfo :: TestResult (Maybe ProcessInfo) -> Process ()
31testLocalDeadProcessInfo result = do
55testLocalDeadProcessInfo result = do
32− pid <- spawnLocal $ do (_ :: String) <- expect; return ()
56+ pid <- spawnLocal $ do "finish" <- expect; return ()
33 mref <- monitor pid
57 mref <- monitor pid
34 send pid "finish"
58 send pid "finish"
35 _ <- receiveWait [
59 _ <- receiveWait [
@@ -52,10 +76,8 @@ testLocalLiveProcessInfo result = do
52 link self
76 link self
53 mRef <- monitor self
77 mRef <- monitor self
54 stash mon mRef
78 stash mon mRef
55− res <- expect
79+ "die" <- expect
56− case res of
57− "die" -> return ()
80+ return ()
58− _ -> die $ "unexpected message received: " ++ res
59
81
60 monRef <- liftIO $ takeMVar mon
82 monRef <- liftIO $ takeMVar mon
61
83
@@ -75,8 +97,8 @@ testLocalLiveProcessInfo result = do
75-- infoMessageQueueLength pInfo == Just 4 &&
97-- infoMessageQueueLength pInfo == Just 4 &&
76 infoRegisteredNames pInfo == ["foobar"]
98 infoRegisteredNames pInfo == ["foobar"]
77
99
78−testRemoteLiveProcessInfo :: TestTransport -> LocalNode -> Assertion
100+testRemoteLiveProcessInfo :: LocalNode -> Assertion
79−testRemoteLiveProcessInfo TestTransport{..} node1 = do
101+testRemoteLiveProcessInfo node1 = do
80 serverAddr <- liftIO $ newEmptyMVar :: IO (MVar ProcessId)
102 serverAddr <- liftIO $ newEmptyMVar :: IO (MVar ProcessId)
81 liftIO $ launchRemote serverAddr
103 liftIO $ launchRemote serverAddr
82 serverPid <- liftIO $ takeMVar serverAddr
104 serverPid <- liftIO $ takeMVar serverAddr
@@ -86,23 +108,21 @@ testRemoteLiveProcessInfo TestTransport{..} node1 = do
86 -- our send op shouldn't overtake link or monitor requests AFAICT
108 -- our send op shouldn't overtake link or monitor requests AFAICT
87 -- so a little table tennis should get us synchronised properly
109 -- so a little table tennis should get us synchronised properly
88 send serverPid (self, "ping")
110 send serverPid (self, "ping")
89− pong <- expect
111+ "pong" <- expect
90 pInfo <- getProcessInfo serverPid
112 pInfo <- getProcessInfo serverPid
91− stash result $ pong == "pong" && pInfo /= Nothing
113+ stash result $ pInfo /= Nothing
92− where
114+ where
93 launchRemote :: MVar ProcessId -> IO ()
115 launchRemote :: MVar ProcessId -> IO ()
94 launchRemote locMV = do
116 launchRemote locMV = do
95− node2 <- liftIO $ newLocalNode testTransport initRemoteTable
117+ node2 <- liftIO $ mkNode "8082"
96 _ <- liftIO $ forkProcess node2 $ do
118 _ <- liftIO $ forkProcess node2 $ do
97 self <- getSelfPid
119 self <- getSelfPid
98 liftIO $ putMVar locMV self
120 liftIO $ putMVar locMV self
99 _ <- receiveWait [
121 _ <- receiveWait [
100− match (\(pid, "ping") -> send pid "pong")
122+ match (\(pid, "ping") -> send pid "pong")
101 ]
123 ]
102− res <- expect
124+ "stop" <- expect
103− case res of
104− "stop" -> return ()
125+ return ()
105− _ -> die $ "unexpected message received: " ++ res
106 return ()
126 return ()
107
127
108 withActiveRemote :: LocalNode
128 withActiveRemote :: LocalNode
@@ -111,9 +131,8 @@ testRemoteLiveProcessInfo TestTransport{..} node1 = do
111 a <- delayedAssertion "getProcessInfo remotePid failed" n True
131 a <- delayedAssertion "getProcessInfo remotePid failed" n True
112 return a
132 return a
113
133
114−tests :: TestTransport -> IO [Test]
134+tests :: LocalNode -> IO [Test]
115−tests testtrans@TestTransport{..} = do
135+tests node1 = do
116− node1 <- newLocalNode testTransport initRemoteTable
117 return [
136 return [
118 testGroup "Process Info" [
137 testGroup "Process Info" [
119 testCase "testLocalDeadProcessInfo"
138 testCase "testLocalDeadProcessInfo"
@@ -125,5 +144,19 @@ tests testtrans@TestTransport{..} = do
125 "expected process-info to be correctly populated"
144 "expected process-info to be correctly populated"
126 node1 True testLocalLiveProcessInfo)
145 node1 True testLocalLiveProcessInfo)
127 , testCase "testRemoveLiveProcessInfo"
146 , testCase "testRemoveLiveProcessInfo"
128− (testRemoteLiveProcessInfo testtrans node1)
147+ (testRemoteLiveProcessInfo node1)
129 ] ]
148 ] ]
149+
150+mkNode :: String -> IO LocalNode
151+mkNode port = do
152+ Right (transport1, _) <- createTransportExposeInternals
153+ "127.0.0.1" port defaultTCPParameters
154+ newLocalNode transport1 initRemoteTable
155+
156+main :: IO ()
157+main = do
158+ node1 <- mkNode "8081"
159+ testData <- tests node1
160+ defaultMain testData
161+ closeLocalNode node1
162+ return ()
distributed-static/ChangeLognew file+16
@@ -0,0 +1,16 @@
1+2012-11-22 Edsko de Vries <edsko@well-typed.com> 0.2.1.1
2+
3+* Relax package bounds to allow for Binary 0.6
4+
5+2012-10-03 Edsko de Vries <edsko@well-typed.com> 0.2.1
6+
7+* Add support for 'staticFlip'
8+
9+2012-08-16 Edsko de Vries <edsko@well-typed.com> 0.2
10+
11+* Hide the 'Closure' constructor and export 'closure' instead so that we are
12+free to change the internal representation
13+
14+2012-08-10 Edsko de Vries <edsko@well-typed.com> 0.1
15+
16+* 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.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.7
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/semantics/CloudHaskellSemantics.pdfnew file

Binary file not shown.

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, duncan@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.11,
23+ containers >= 0.4 && < 0.6
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+30
@@ -0,0 +1,30 @@
1+2012-10-19 Edsko de Vries <edsko@well-typed.com> 0.3.1
2+
3+* Bugfix. Reconnecting between endpoints did not work under certain
4+circumstances.
5+
6+2012-10-03 Edsko de Vries <edsko@well-typed.com> 0.3.0
7+
8+* Implement new disconnection semantics
9+* Make 'connect' asynchronous (sender allocated connection IDs)
10+* Fix distributed deadlock
11+* Optimize treatment of crossed connection requests
12+* Relax upper bound on network
13+* Fix memory leaks
14+
15+2012-08-20 Edsko de Vries <edsko@well-typed.com> 0.2.0.3
16+
17+* Allow for colons in hostnames (for IPv6)
18+
19+2012-07-16 Edsko de Vries <edsko@well-typed.com> 0.2.0.2
20+
21+* Base 4.6 compatibility
22+* Relax package contraints on bytestring and containers
23+
24+2012-07-12 Edsko de Vries <edsko@well-typed.com> 0.2.0.1
25+
26+* Fix bug in recvExact
27+
28+2012-07-07 Edsko de Vries <edsko@well-typed.com> 0.2.0
29+
30+* 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+88
@@ -0,0 +1,88 @@
1+Name: network-transport-tcp
2+Version: 0.3.1
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, duncan@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+Flag use-mock-network
24+ Description: Use mock network implementation (for testing)
25+ Default: False
26+
27+Library
28+ Build-Depends: base >= 4.3 && < 5,
29+ network-transport >= 0.3 && < 0.4,
30+ data-accessor >= 0.2 && < 0.3,
31+ containers >= 0.4 && < 0.6,
32+ bytestring >= 0.9 && < 0.11,
33+ network >= 2.3 && < 2.5
34+ Exposed-modules: Network.Transport.TCP,
35+ Network.Transport.TCP.Internal
36+ Extensions: CPP
37+ ghc-options: -Wall -fno-warn-unused-do-bind
38+ HS-Source-Dirs: src
39+ If flag(use-mock-network)
40+ CPP-Options: -DUSE_MOCK_NETWORK
41+ Exposed-modules: Network.Transport.TCP.Mock.Socket
42+ Network.Transport.TCP.Mock.Socket.ByteString
43+
44+Test-Suite TestTCP
45+ Type: exitcode-stdio-1.0
46+ Main-Is: TestTCP.hs
47+ Build-Depends: base >= 4.3 && < 5,
48+ network-transport-tests >= 0.1.0.1 && < 0.2,
49+ network >= 2.3 && < 2.5,
50+ network-transport >= 0.3 && < 0.4,
51+ network-transport-tcp >= 0.3 && < 0.4
52+ ghc-options: -threaded -rtsopts -with-rtsopts=-N
53+ HS-Source-Dirs: tests
54+ Extensions: CPP,
55+ OverloadedStrings
56+ If flag(use-mock-network)
57+ CPP-Options: -DUSE_MOCK_NETWORK
58+
59+Test-Suite TestQC
60+ Type: exitcode-stdio-1.0
61+ Main-Is: TestQC.hs
62+ If flag(use-mock-network)
63+ Build-Depends: base >= 4.3 && < 5,
64+ test-framework,
65+ test-framework-quickcheck2,
66+ test-framework-hunit,
67+ QuickCheck,
68+ HUnit,
69+ network-transport,
70+ network-transport-tcp,
71+ containers,
72+ bytestring,
73+ pretty,
74+ data-accessor,
75+ data-accessor-transformers,
76+ mtl,
77+ transformers,
78+ lockfree-queue
79+ Else
80+ Buildable: False
81+ ghc-options: -threaded -Wall -fno-warn-orphans
82+ HS-Source-Dirs: tests
83+ Extensions: TypeSynonymInstances
84+ FlexibleInstances
85+ OverlappingInstances
86+ OverloadedStrings
87+ DeriveDataTypeable
88+ MultiParamTypeClasses
network-transport-tcp/src/Network/Transport/TCP.hsnew file+1651
@@ -0,0 +1,1651 @@
1+-- | TCP implementation of the transport layer.
2+--
3+-- The TCP implementation guarantees that only a single TCP connection (socket)
4+-- will be used between endpoints, provided that the addresses specified are
5+-- canonical. If /A/ connects to /B/ and reports its address as
6+-- @192.168.0.1:8080@ and /B/ subsequently connects tries to connect to /A/ as
7+-- @client1.local:http-alt@ then the transport layer will not realize that the
8+-- TCP connection can be reused.
9+--
10+-- Applications that use the TCP transport should use
11+-- 'Network.Socket.withSocketsDo' in their main function for Windows
12+-- compatibility (see "Network.Socket").
13+module Network.Transport.TCP
14+ ( -- * Main API
15+ createTransport
16+ , TCPParameters(..)
17+ , defaultTCPParameters
18+ -- * Internals (exposed for unit tests)
19+ , createTransportExposeInternals
20+ , TransportInternals(..)
21+ , EndPointId
22+ , encodeEndPointAddress
23+ , decodeEndPointAddress
24+ , ControlHeader(..)
25+ , ConnectionRequestResponse(..)
26+ , firstNonReservedLightweightConnectionId
27+ , firstNonReservedHeavyweightConnectionId
28+ , socketToEndPoint
29+ , LightweightConnectionId
30+ -- * Design notes
31+ -- $design
32+ ) where
33+
34+import Prelude hiding
35+ ( mapM_
36+#if ! MIN_VERSION_base(4,6,0)
37+ , catch
38+#endif
39+ )
40+
41+import Network.Transport
42+import Network.Transport.TCP.Internal
43+ ( forkServer
44+ , recvWithLength
45+ , recvInt32
46+ , tryCloseSocket
47+ )
48+import Network.Transport.Internal
49+ ( encodeInt32
50+ , prependLength
51+ , mapIOException
52+ , tryIO
53+ , tryToEnum
54+ , void
55+ , timeoutMaybe
56+ , asyncWhenCancelled
57+ )
58+
59+#ifdef USE_MOCK_NETWORK
60+import qualified Network.Transport.TCP.Mock.Socket as N
61+#else
62+import qualified Network.Socket as N
63+#endif
64+ ( HostName
65+ , ServiceName
66+ , Socket
67+ , getAddrInfo
68+ , socket
69+ , addrFamily
70+ , addrAddress
71+ , SocketType(Stream)
72+ , defaultProtocol
73+ , setSocketOption
74+ , SocketOption(ReuseAddr)
75+ , connect
76+ , sOMAXCONN
77+ , AddrInfo
78+ )
79+
80+#ifdef USE_MOCK_NETWORK
81+import Network.Transport.TCP.Mock.Socket.ByteString (sendMany)
82+#else
83+import Network.Socket.ByteString (sendMany)
84+#endif
85+
86+import Control.Concurrent (forkIO, ThreadId, killThread, myThreadId)
87+import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan)
88+import Control.Concurrent.MVar
89+ ( MVar
90+ , newMVar
91+ , modifyMVar
92+ , modifyMVar_
93+ , readMVar
94+ , putMVar
95+ , newEmptyMVar
96+ , withMVar
97+ )
98+import Control.Category ((>>>))
99+import Control.Applicative ((<$>))
100+import Control.Monad (when, unless, join)
101+import Control.Exception
102+ ( IOException
103+ , SomeException
104+ , AsyncException
105+ , handle
106+ , throw
107+ , throwIO
108+ , try
109+ , bracketOnError
110+ , fromException
111+ , catch
112+ )
113+import Data.IORef (IORef, newIORef, writeIORef, readIORef)
114+import Data.ByteString (ByteString)
115+import qualified Data.ByteString as BS (concat)
116+import qualified Data.ByteString.Char8 as BSC (pack, unpack)
117+import Data.Bits (shiftL, (.|.))
118+import Data.Word (Word32)
119+import Data.Set (Set)
120+import qualified Data.Set as Set
121+ ( empty
122+ , insert
123+ , elems
124+ , singleton
125+ , null
126+ , delete
127+ , member
128+ )
129+import Data.Map (Map)
130+import qualified Data.Map as Map (empty)
131+import Data.Accessor (Accessor, accessor, (^.), (^=), (^:))
132+import qualified Data.Accessor.Container as DAC (mapMaybe)
133+import Data.Foldable (forM_, mapM_)
134+
135+-- $design
136+--
137+-- [Goals]
138+--
139+-- The TCP transport maps multiple logical connections between /A/ and /B/ (in
140+-- either direction) to a single TCP connection:
141+--
142+-- > +-------+ +-------+
143+-- > | A |==========================| B |
144+-- > | |>~~~~~~~~~~~~~~~~~~~~~~~~~|~~~\ |
145+-- > | Q |>~~~~~~~~~~~~~~~~~~~~~~~~~|~~~Q |
146+-- > | \~~~|~~~~~~~~~~~~~~~~~~~~~~~~~<| |
147+-- > | |==========================| |
148+-- > +-------+ +-------+
149+--
150+-- Ignoring the complications detailed below, the TCP connection is set up is
151+-- when the first lightweight connection is created (in either direction), and
152+-- torn down when the last lightweight connection (in either direction) is
153+-- closed.
154+--
155+-- [Connecting]
156+--
157+-- Let /A/, /B/ be two endpoints without any connections. When /A/ wants to
158+-- connect to /B/, it locally records that it is trying to connect to /B/ and
159+-- sends a request to /B/. As part of the request /A/ sends its own endpoint
160+-- address to /B/ (so that /B/ can reuse the connection in the other direction).
161+--
162+-- When /B/ receives the connection request it first checks if it did not
163+-- already initiate a connection request to /A/. If not it will acknowledge the
164+-- connection request by sending 'ConnectionRequestAccepted' to /A/ and record
165+-- that it has a TCP connection to /A/.
166+--
167+-- The tricky case arises when /A/ sends a connection request to /B/ and /B/
168+-- finds that it had already sent a connection request to /A/. In this case /B/
169+-- will accept the connection request from /A/ if /A/s endpoint address is
170+-- smaller (lexicographically) than /B/s, and reject it otherwise. If it rejects
171+-- it, it sends a 'ConnectionRequestCrossed' message to /A/. (The
172+-- lexicographical ordering is an arbitrary but convenient way to break the
173+-- tie.)
174+--
175+-- When it receives a 'ConnectionRequestCrossed' message the /A/ thread that
176+-- initiated the request just needs to wait until the /A/ thread that is dealing
177+-- with /B/'s connection request completes.
178+--
179+-- [Disconnecting]
180+--
181+-- The TCP connection is created as soon as the first logical connection from
182+-- /A/ to /B/ (or /B/ to /A/) is established. At this point a thread (@#@) is
183+-- spawned that listens for incoming connections from /B/:
184+--
185+-- > +-------+ +-------+
186+-- > | A |==========================| B |
187+-- > | |>~~~~~~~~~~~~~~~~~~~~~~~~~|~~~\ |
188+-- > | | | Q |
189+-- > | #| | |
190+-- > | |==========================| |
191+-- > +-------+ +-------+
192+--
193+-- The question is when the TCP connection can be closed again. Conceptually,
194+-- we want to do reference counting: when there are no logical connections left
195+-- between /A/ and /B/ we want to close the socket (possibly after some
196+-- timeout).
197+--
198+-- However, /A/ and /B/ need to agree that the refcount has reached zero. It
199+-- might happen that /B/ sends a connection request over the existing socket at
200+-- the same time that /A/ closes its logical connection to /B/ and closes the
201+-- socket. This will cause a failure in /B/ (which will have to retry) which is
202+-- not caused by a network failure, which is unfortunate. (Note that the
203+-- connection request from /B/ might succeed even if /A/ closes the socket.)
204+--
205+-- Instead, when /A/ is ready to close the socket it sends a 'CloseSocket'
206+-- request to /B/ and records that its connection to /B/ is closing. If /A/
207+-- receives a new connection request from /B/ after having sent the
208+-- 'CloseSocket' request it simply forgets that it sent a 'CloseSocket' request
209+-- and increments the reference count of the connection again.
210+--
211+-- When /B/ receives a 'CloseSocket' message and it too is ready to close the
212+-- connection, it will respond with a reciprocal 'CloseSocket' request to /A/
213+-- and then actually close the socket. /A/ meanwhile will not send any more
214+-- requests to /B/ after having sent a 'CloseSocket' request, and will actually
215+-- close its end of the socket only when receiving the 'CloseSocket' message
216+-- from /B/. (Since /A/ recorded that its connection to /B/ is in closing state
217+-- after sending a 'CloseSocket' request to /B/, it knows not to reciprocate /B/
218+-- reciprocal 'CloseSocket' message.)
219+--
220+-- If there is a concurrent thread in /A/ waiting to connect to /B/ after /A/
221+-- has sent a 'CloseSocket' request then this thread will block until /A/ knows
222+-- whether to reuse the old socket (if /B/ sends a new connection request
223+-- instead of acknowledging the 'CloseSocket') or to set up a new socket.
224+
225+--------------------------------------------------------------------------------
226+-- Internal datatypes --
227+--------------------------------------------------------------------------------
228+
229+-- We use underscores for fields that we might update (using accessors)
230+--
231+-- All data types follow the same structure:
232+--
233+-- * A top-level data type describing static properties (TCPTransport,
234+-- LocalEndPoint, RemoteEndPoint)
235+-- * The 'static' properties include an MVar containing a data structure for
236+-- the dynamic properties (TransportState, LocalEndPointState,
237+-- RemoteEndPointState). The state could be invalid/valid/closed,/etc.
238+-- * For the case of "valid" we use third data structure to give more details
239+-- about the state (ValidTransportState, ValidLocalEndPointState,
240+-- ValidRemoteEndPointState).
241+
242+data TCPTransport = TCPTransport
243+ { transportHost :: !N.HostName
244+ , transportPort :: !N.ServiceName
245+ , transportState :: !(MVar TransportState)
246+ , transportParams :: !TCPParameters
247+ }
248+
249+data TransportState =
250+ TransportValid !ValidTransportState
251+ | TransportClosed
252+
253+data ValidTransportState = ValidTransportState
254+ { _localEndPoints :: !(Map EndPointAddress LocalEndPoint)
255+ , _nextEndPointId :: !EndPointId
256+ }
257+
258+data LocalEndPoint = LocalEndPoint
259+ { localAddress :: !EndPointAddress
260+ , localChannel :: !(Chan Event)
261+ , localState :: !(MVar LocalEndPointState)
262+ }
263+
264+data LocalEndPointState =
265+ LocalEndPointValid !ValidLocalEndPointState
266+ | LocalEndPointClosed
267+
268+data ValidLocalEndPointState = ValidLocalEndPointState
269+ { -- Next available ID for an outgoing lightweight self-connection
270+ -- (see also remoteNextConnOutId)
271+ _localNextConnOutId :: !LightweightConnectionId
272+ -- Next available ID for an incoming heavyweight connection
273+ , _nextConnInId :: !HeavyweightConnectionId
274+ -- Currently active outgoing heavyweight connections
275+ , _localConnections :: !(Map EndPointAddress RemoteEndPoint)
276+ }
277+
278+-- REMOTE ENDPOINTS
279+--
280+-- Remote endpoints (basically, TCP connections) have the following lifecycle:
281+--
282+-- Init ---+---> Invalid
283+-- |
284+-- +-------------------------------\
285+-- | |
286+-- | /----------\ |
287+-- | | | |
288+-- | v | v
289+-- +---> Valid ---> Closing ---> Closed
290+-- | | | |
291+-- | | | v
292+-- \-------+----------+--------> Failed
293+--
294+-- Init: There are two places where we create new remote endpoints: in
295+-- createConnectionTo (in response to an API 'connect' call) and in
296+-- handleConnectionRequest (when a remote node tries to connect to us).
297+-- 'Init' carries an MVar () 'resolved' which concurrent threads can use to
298+-- wait for the remote endpoint to finish initialization. We record who
299+-- requested the connection (the local endpoint or the remote endpoint).
300+--
301+-- Invalid: We put the remote endpoint in invalid state only during
302+-- createConnectionTo when we fail to connect.
303+--
304+-- Valid: This is the "normal" state for a working remote endpoint.
305+--
306+-- Closing: When we detect that a remote endpoint is no longer used, we send a
307+-- CloseSocket request across the connection and put the remote endpoint in
308+-- closing state. As with Init, 'Closing' carries an MVar () 'resolved' which
309+-- concurrent threads can use to wait for the remote endpoint to either be
310+-- closed fully (if the communication parnet responds with another
311+-- CloseSocket) or be put back in 'Valid' state if the remote endpoint denies
312+-- the request.
313+--
314+-- We also put the endpoint in Closed state, directly from Init, if we our
315+-- outbound connection request crossed an inbound connection request and we
316+-- decide to keep the inbound (i.e., the remote endpoint sent us a
317+-- ConnectionRequestCrossed message).
318+--
319+-- Closed: The endpoint is put in Closed state after a successful garbage
320+-- collection.
321+--
322+-- Failed: If the connection to the remote endpoint is lost, or the local
323+-- endpoint (or the whole transport) is closed manually, the remote endpoint is
324+-- put in Failed state, and we record the reason.
325+--
326+-- Invariants for dealing with remote endpoints:
327+--
328+-- INV-SEND: Whenever we send data the remote endpoint must be locked (to avoid
329+-- interleaving bits of payload).
330+--
331+-- INV-CLOSE: Local endpoints should never point to remote endpoint in closed
332+-- state. Whenever we put an endpoint in Closed state we remove that
333+-- endpoint from localConnections first, so that if a concurrent thread reads
334+-- the MVar, finds RemoteEndPointClosed, and then looks up the endpoint in
335+-- localConnections it is guaranteed to either find a different remote
336+-- endpoint, or else none at all (if we don't insist in this order some
337+-- threads might start spinning).
338+--
339+-- INV-RESOLVE: We should only signal on 'resolved' while the remote endpoint is
340+-- locked, and the remote endpoint must be in Valid or Closed state once
341+-- unlocked. This guarantees that there will not be two threads attempting to
342+-- both signal on 'resolved'.
343+--
344+-- INV-LOST: If a send or recv fails, or a socket is closed unexpectedly, we
345+-- first put the remote endpoint in Closed state, and then send a
346+-- EventConnectionLost event. This guarantees that we only send this event
347+-- once.
348+--
349+-- INV-CLOSING: An endpoint in closing state is for all intents and purposes
350+-- closed; that is, we shouldn't do any 'send's on it (although 'recv' is
351+-- acceptable, of course -- as we are waiting for the remote endpoint to
352+-- confirm or deny the request).
353+--
354+-- INV-LOCK-ORDER: Remote endpoint must be locked before their local endpoints.
355+-- In other words: it is okay to call modifyMVar on a local endpoint inside a
356+-- modifyMVar on a remote endpoint, but not the other way around. In
357+-- particular, it is okay to call removeRemoteEndPoint inside
358+-- modifyRemoteState.
359+
360+data RemoteEndPoint = RemoteEndPoint
361+ { remoteAddress :: !EndPointAddress
362+ , remoteState :: !(MVar RemoteState)
363+ , remoteId :: !HeavyweightConnectionId
364+ , remoteScheduled :: !(Chan (IO ()))
365+ }
366+
367+data RequestedBy = RequestedByUs | RequestedByThem
368+ deriving (Eq, Show)
369+
370+data RemoteState =
371+ -- | Invalid remote endpoint (for example, invalid address)
372+ RemoteEndPointInvalid !(TransportError ConnectErrorCode)
373+ -- | The remote endpoint is being initialized
374+ | RemoteEndPointInit !(MVar ()) !(MVar ()) !RequestedBy
375+ -- | "Normal" working endpoint
376+ | RemoteEndPointValid !ValidRemoteEndPointState
377+ -- | The remote endpoint is being closed (garbage collected)
378+ | RemoteEndPointClosing !(MVar ()) !ValidRemoteEndPointState
379+ -- | The remote endpoint has been closed (garbage collected)
380+ | RemoteEndPointClosed
381+ -- | The remote endpoint has failed, or has been forcefully shutdown
382+ -- using a closeTransport or closeEndPoint API call
383+ | RemoteEndPointFailed !IOException
384+
385+-- TODO: we might want to replace Set (here and elsewhere) by faster
386+-- containers
387+--
388+-- TODO: we could get rid of 'remoteIncoming' (and maintain less state) if
389+-- we introduce a new event 'AllConnectionsClosed'
390+data ValidRemoteEndPointState = ValidRemoteEndPointState
391+ { _remoteOutgoing :: !Int
392+ , _remoteIncoming :: !(Set LightweightConnectionId)
393+ , _remoteMaxIncoming :: !LightweightConnectionId
394+ , _remoteNextConnOutId :: !LightweightConnectionId
395+ , remoteSocket :: !N.Socket
396+ , remoteSendLock :: !(MVar ())
397+ }
398+
399+-- | Local identifier for an endpoint within this transport
400+type EndPointId = Word32
401+
402+-- | Pair of local and a remote endpoint (for conciseness in signatures)
403+type EndPointPair = (LocalEndPoint, RemoteEndPoint)
404+
405+-- | Lightweight connection ID (sender allocated)
406+--
407+-- A ConnectionId is the concentation of a 'HeavyweightConnectionId' and a
408+-- 'LightweightConnectionId'.
409+type LightweightConnectionId = Word32
410+
411+-- | Heavyweight connection ID (recipient allocated)
412+--
413+-- A ConnectionId is the concentation of a 'HeavyweightConnectionId' and a
414+-- 'LightweightConnectionId'.
415+type HeavyweightConnectionId = Word32
416+
417+-- | Control headers
418+data ControlHeader =
419+ -- | Tell the remote endpoint that we created a new connection
420+ CreatedNewConnection
421+ -- | Tell the remote endpoint we will no longer be using a connection
422+ | CloseConnection
423+ -- | Request to close the connection (see module description)
424+ | CloseSocket
425+ deriving (Enum, Bounded, Show)
426+
427+-- | Response sent by /B/ to /A/ when /A/ tries to connect
428+data ConnectionRequestResponse =
429+ -- | /B/ accepts the connection
430+ ConnectionRequestAccepted
431+ -- | /A/ requested an invalid endpoint
432+ | ConnectionRequestInvalid
433+ -- | /A/s request crossed with a request from /B/ (see protocols)
434+ | ConnectionRequestCrossed
435+ deriving (Enum, Bounded, Show)
436+
437+-- | Parameters for setting up the TCP transport
438+data TCPParameters = TCPParameters {
439+ -- | Backlog for 'listen'.
440+ -- Defaults to SOMAXCONN.
441+ tcpBacklog :: Int
442+ -- | Should we set SO_REUSEADDR on the server socket?
443+ -- Defaults to True.
444+ , tcpReuseServerAddr :: Bool
445+ -- | Should we set SO_REUSEADDR on client sockets?
446+ -- Defaults to True.
447+ , tcpReuseClientAddr :: Bool
448+ }
449+
450+-- | Internal functionality we expose for unit testing
451+data TransportInternals = TransportInternals
452+ { -- | The ID of the thread that listens for new incoming connections
453+ transportThread :: ThreadId
454+ -- | Find the socket between a local and a remote endpoint
455+ , socketBetween :: EndPointAddress
456+ -> EndPointAddress
457+ -> IO N.Socket
458+ }
459+
460+--------------------------------------------------------------------------------
461+-- Top-level functionality --
462+--------------------------------------------------------------------------------
463+
464+-- | Create a TCP transport
465+createTransport :: N.HostName
466+ -> N.ServiceName
467+ -> TCPParameters
468+ -> IO (Either IOException Transport)
469+createTransport host port params =
470+ either Left (Right . fst) <$> createTransportExposeInternals host port params
471+
472+-- | You should probably not use this function (used for unit testing only)
473+createTransportExposeInternals
474+ :: N.HostName
475+ -> N.ServiceName
476+ -> TCPParameters
477+ -> IO (Either IOException (Transport, TransportInternals))
478+createTransportExposeInternals host port params = do
479+ state <- newMVar . TransportValid $ ValidTransportState
480+ { _localEndPoints = Map.empty
481+ , _nextEndPointId = 0
482+ }
483+ let transport = TCPTransport { transportState = state
484+ , transportHost = host
485+ , transportPort = port
486+ , transportParams = params
487+ }
488+ tryIO $ bracketOnError (forkServer
489+ host
490+ port
491+ (tcpBacklog params)
492+ (tcpReuseServerAddr params)
493+ (terminationHandler transport)
494+ (handleConnectionRequest transport))
495+ killThread
496+ (mkTransport transport)
497+ where
498+ mkTransport :: TCPTransport
499+ -> ThreadId
500+ -> IO (Transport, TransportInternals)
501+ mkTransport transport tid = return
502+ ( Transport
503+ { newEndPoint = apiNewEndPoint transport
504+ , closeTransport = let evs = [ EndPointClosed
505+ , throw $ userError "Transport closed"
506+ ] in
507+ apiCloseTransport transport (Just tid) evs
508+ }
509+ , TransportInternals
510+ { transportThread = tid
511+ , socketBetween = internalSocketBetween transport
512+ }
513+ )
514+
515+ terminationHandler :: TCPTransport -> SomeException -> IO ()
516+ terminationHandler transport ex = do
517+ let evs = [ ErrorEvent (TransportError EventTransportFailed (show ex))
518+ , throw $ userError "Transport closed"
519+ ]
520+ apiCloseTransport transport Nothing evs
521+
522+-- | Default TCP parameters
523+defaultTCPParameters :: TCPParameters
524+defaultTCPParameters = TCPParameters {
525+ tcpBacklog = N.sOMAXCONN
526+ , tcpReuseServerAddr = True
527+ , tcpReuseClientAddr = True
528+ }
529+
530+--------------------------------------------------------------------------------
531+-- API functions --
532+--------------------------------------------------------------------------------
533+
534+-- | Close the transport
535+apiCloseTransport :: TCPTransport -> Maybe ThreadId -> [Event] -> IO ()
536+apiCloseTransport transport mTransportThread evs =
537+ asyncWhenCancelled return $ do
538+ mTSt <- modifyMVar (transportState transport) $ \st -> case st of
539+ TransportValid vst -> return (TransportClosed, Just vst)
540+ TransportClosed -> return (TransportClosed, Nothing)
541+ forM_ mTSt $ mapM_ (apiCloseEndPoint transport evs) . (^. localEndPoints)
542+ -- This will invoke the termination handler, which in turn will call
543+ -- apiCloseTransport again, but then the transport will already be closed
544+ -- and we won't be passed a transport thread, so we terminate immmediate
545+ forM_ mTransportThread killThread
546+
547+-- | Create a new endpoint
548+apiNewEndPoint :: TCPTransport
549+ -> IO (Either (TransportError NewEndPointErrorCode) EndPoint)
550+apiNewEndPoint transport =
551+ try . asyncWhenCancelled closeEndPoint $ do
552+ ourEndPoint <- createLocalEndPoint transport
553+ return EndPoint
554+ { receive = readChan (localChannel ourEndPoint)
555+ , address = localAddress ourEndPoint
556+ , connect = apiConnect (transportParams transport) ourEndPoint
557+ , closeEndPoint = let evs = [ EndPointClosed
558+ , throw $ userError "Endpoint closed"
559+ ] in
560+ apiCloseEndPoint transport evs ourEndPoint
561+ , newMulticastGroup = return . Left $ newMulticastGroupError
562+ , resolveMulticastGroup = return . Left . const resolveMulticastGroupError
563+ }
564+ where
565+ newMulticastGroupError =
566+ TransportError NewMulticastGroupUnsupported "Multicast not supported"
567+ resolveMulticastGroupError =
568+ TransportError ResolveMulticastGroupUnsupported "Multicast not supported"
569+
570+-- | Connnect to an endpoint
571+apiConnect :: TCPParameters -- ^ Parameters
572+ -> LocalEndPoint -- ^ Local end point
573+ -> EndPointAddress -- ^ Remote address
574+ -> Reliability -- ^ Reliability (ignored)
575+ -> ConnectHints -- ^ Hints
576+ -> IO (Either (TransportError ConnectErrorCode) Connection)
577+apiConnect params ourEndPoint theirAddress _reliability hints =
578+ try . asyncWhenCancelled close $
579+ if localAddress ourEndPoint == theirAddress
580+ then connectToSelf ourEndPoint
581+ else do
582+ resetIfBroken ourEndPoint theirAddress
583+ (theirEndPoint, connId) <-
584+ createConnectionTo params ourEndPoint theirAddress hints
585+ -- connAlive can be an IORef rather than an MVar because it is protected
586+ -- by the remoteState MVar. We don't need the overhead of locking twice.
587+ connAlive <- newIORef True
588+ return Connection
589+ { send = apiSend (ourEndPoint, theirEndPoint) connId connAlive
590+ , close = apiClose (ourEndPoint, theirEndPoint) connId connAlive
591+ }
592+
593+-- | Close a connection
594+apiClose :: EndPointPair -> LightweightConnectionId -> IORef Bool -> IO ()
595+apiClose (ourEndPoint, theirEndPoint) connId connAlive =
596+ void . tryIO . asyncWhenCancelled return $ do
597+ mAct <- modifyMVar (remoteState theirEndPoint) $ \st -> case st of
598+ RemoteEndPointValid vst -> do
599+ alive <- readIORef connAlive
600+ if alive
601+ then do
602+ writeIORef connAlive False
603+ act <- schedule theirEndPoint $
604+ sendOn vst [encodeInt32 CloseConnection, encodeInt32 connId]
605+ return ( RemoteEndPointValid
606+ . (remoteOutgoing ^: (\x -> x - 1))
607+ $ vst
608+ , Just act
609+ )
610+ else
611+ return (RemoteEndPointValid vst, Nothing)
612+ _ ->
613+ return (st, Nothing)
614+ forM_ mAct $ runScheduledAction (ourEndPoint, theirEndPoint)
615+ closeIfUnused (ourEndPoint, theirEndPoint)
616+
617+
618+-- | Send data across a connection
619+apiSend :: EndPointPair -- ^ Local and remote endpoint
620+ -> LightweightConnectionId -- ^ Connection ID
621+ -> IORef Bool -- ^ Is the connection still alive?
622+ -> [ByteString] -- ^ Payload
623+ -> IO (Either (TransportError SendErrorCode) ())
624+apiSend (ourEndPoint, theirEndPoint) connId connAlive payload =
625+ -- We don't need the overhead of asyncWhenCancelled here
626+ try . mapIOException sendFailed $ do
627+ act <- withMVar (remoteState theirEndPoint) $ \st -> case st of
628+ RemoteEndPointInvalid _ ->
629+ relyViolation (ourEndPoint, theirEndPoint) "apiSend"
630+ RemoteEndPointInit _ _ _ ->
631+ relyViolation (ourEndPoint, theirEndPoint) "apiSend"
632+ RemoteEndPointValid vst -> do
633+ alive <- readIORef connAlive
634+ if alive
635+ then schedule theirEndPoint $
636+ sendOn vst (encodeInt32 connId : prependLength payload)
637+ else throwIO $ TransportError SendClosed "Connection closed"
638+ RemoteEndPointClosing _ _ -> do
639+ alive <- readIORef connAlive
640+ if alive
641+ then relyViolation (ourEndPoint, theirEndPoint) "apiSend"
642+ else throwIO $ TransportError SendClosed "Connection closed"
643+ RemoteEndPointClosed -> do
644+ alive <- readIORef connAlive
645+ if alive
646+ then relyViolation (ourEndPoint, theirEndPoint) "apiSend"
647+ else throwIO $ TransportError SendClosed "Connection closed"
648+ RemoteEndPointFailed err -> do
649+ alive <- readIORef connAlive
650+ if alive
651+ then throwIO $ TransportError SendFailed (show err)
652+ else throwIO $ TransportError SendClosed "Connection closed"
653+ runScheduledAction (ourEndPoint, theirEndPoint) act
654+ where
655+ sendFailed = TransportError SendFailed . show
656+
657+-- | Force-close the endpoint
658+apiCloseEndPoint :: TCPTransport -- ^ Transport
659+ -> [Event] -- ^ Events used to report closure
660+ -> LocalEndPoint -- ^ Local endpoint
661+ -> IO ()
662+apiCloseEndPoint transport evs ourEndPoint =
663+ asyncWhenCancelled return $ do
664+ -- Remove the reference from the transport state
665+ removeLocalEndPoint transport ourEndPoint
666+ -- Close the local endpoint
667+ mOurState <- modifyMVar (localState ourEndPoint) $ \st ->
668+ case st of
669+ LocalEndPointValid vst ->
670+ return (LocalEndPointClosed, Just vst)
671+ LocalEndPointClosed ->
672+ return (LocalEndPointClosed, Nothing)
673+ forM_ mOurState $ \vst -> do
674+ forM_ (vst ^. localConnections) tryCloseRemoteSocket
675+ forM_ evs $ writeChan (localChannel ourEndPoint)
676+ where
677+ -- Close the remote socket and return the set of all incoming connections
678+ tryCloseRemoteSocket :: RemoteEndPoint -> IO ()
679+ tryCloseRemoteSocket theirEndPoint = do
680+ -- We make an attempt to close the connection nicely
681+ -- (by sending a CloseSocket first)
682+ let closed = RemoteEndPointFailed . userError $ "apiCloseEndPoint"
683+ mAct <- modifyMVar (remoteState theirEndPoint) $ \st ->
684+ case st of
685+ RemoteEndPointInvalid _ ->
686+ return (st, Nothing)
687+ RemoteEndPointInit resolved _ _ -> do
688+ putMVar resolved ()
689+ return (closed, Nothing)
690+ RemoteEndPointValid vst -> do
691+ act <- schedule theirEndPoint $ do
692+ tryIO $ sendOn vst [ encodeInt32 CloseSocket
693+ , encodeInt32 (vst ^. remoteMaxIncoming)
694+ ]
695+ tryCloseSocket (remoteSocket vst)
696+ return (closed, Just act)
697+ RemoteEndPointClosing resolved vst -> do
698+ putMVar resolved ()
699+ act <- schedule theirEndPoint $ tryCloseSocket (remoteSocket vst)
700+ return (closed, Just act)
701+ RemoteEndPointClosed ->
702+ return (st, Nothing)
703+ RemoteEndPointFailed err ->
704+ return (RemoteEndPointFailed err, Nothing)
705+ forM_ mAct $ runScheduledAction (ourEndPoint, theirEndPoint)
706+
707+
708+--------------------------------------------------------------------------------
709+-- Incoming requests --
710+--------------------------------------------------------------------------------
711+
712+-- | Handle a connection request (that is, a remote endpoint that is trying to
713+-- establish a TCP connection with us)
714+--
715+-- 'handleConnectionRequest' runs in the context of the transport thread, which
716+-- can be killed asynchronously by 'closeTransport'. We fork a separate thread
717+-- as soon as we have located the lcoal endpoint that the remote endpoint is
718+-- interested in. We cannot fork any sooner because then we have no way of
719+-- storing the thread ID and hence no way of killing the thread when we take
720+-- the transport down. We must be careful to close the socket when a (possibly
721+-- asynchronous, ThreadKilled) exception occurs. (If an exception escapes from
722+-- handleConnectionRequest the transport will be shut down.)
723+handleConnectionRequest :: TCPTransport -> N.Socket -> IO ()
724+handleConnectionRequest transport sock = handle handleException $ do
725+ ourEndPointId <- recvInt32 sock
726+ theirAddress <- EndPointAddress . BS.concat <$> recvWithLength sock
727+ let ourAddress = encodeEndPointAddress (transportHost transport)
728+ (transportPort transport)
729+ ourEndPointId
730+ ourEndPoint <- withMVar (transportState transport) $ \st -> case st of
731+ TransportValid vst ->
732+ case vst ^. localEndPointAt ourAddress of
733+ Nothing -> do
734+ sendMany sock [encodeInt32 ConnectionRequestInvalid]
735+ throwIO $ userError "handleConnectionRequest: Invalid endpoint"
736+ Just ourEndPoint ->
737+ return ourEndPoint
738+ TransportClosed ->
739+ throwIO $ userError "Transport closed"
740+ void . forkIO $ go ourEndPoint theirAddress
741+ where
742+ go :: LocalEndPoint -> EndPointAddress -> IO ()
743+ go ourEndPoint theirAddress = do
744+ -- This runs in a thread that will never be killed
745+ mEndPoint <- handle ((>> return Nothing) . handleException) $ do
746+ resetIfBroken ourEndPoint theirAddress
747+ (theirEndPoint, isNew) <-
748+ findRemoteEndPoint ourEndPoint theirAddress RequestedByThem
749+
750+ if not isNew
751+ then do
752+ tryIO $ sendMany sock [encodeInt32 ConnectionRequestCrossed]
753+ tryCloseSocket sock
754+ return Nothing
755+ else do
756+ sendLock <- newMVar ()
757+ let vst = ValidRemoteEndPointState
758+ { remoteSocket = sock
759+ , remoteSendLock = sendLock
760+ , _remoteOutgoing = 0
761+ , _remoteIncoming = Set.empty
762+ , _remoteMaxIncoming = 0
763+ , _remoteNextConnOutId = firstNonReservedLightweightConnectionId
764+ }
765+ sendMany sock [encodeInt32 ConnectionRequestAccepted]
766+ resolveInit (ourEndPoint, theirEndPoint) (RemoteEndPointValid vst)
767+ return (Just theirEndPoint)
768+ -- If we left the scope of the exception handler with a return value of
769+ -- Nothing then the socket is already closed; otherwise, the socket has
770+ -- been recorded as part of the remote endpoint. Either way, we no longer
771+ -- have to worry about closing the socket on receiving an asynchronous
772+ -- exception from this point forward.
773+ forM_ mEndPoint $ handleIncomingMessages . (,) ourEndPoint
774+
775+ handleException :: SomeException -> IO ()
776+ handleException ex = do
777+ tryCloseSocket sock
778+ rethrowIfAsync (fromException ex)
779+
780+ rethrowIfAsync :: Maybe AsyncException -> IO ()
781+ rethrowIfAsync = mapM_ throwIO
782+
783+-- | Handle requests from a remote endpoint.
784+--
785+-- Returns only if the remote party closes the socket or if an error occurs.
786+-- This runs in a thread that will never be killed.
787+handleIncomingMessages :: EndPointPair -> IO ()
788+handleIncomingMessages (ourEndPoint, theirEndPoint) = do
789+ mSock <- withMVar theirState $ \st ->
790+ case st of
791+ RemoteEndPointInvalid _ ->
792+ relyViolation (ourEndPoint, theirEndPoint)
793+ "handleIncomingMessages (invalid)"
794+ RemoteEndPointInit _ _ _ ->
795+ relyViolation (ourEndPoint, theirEndPoint)
796+ "handleIncomingMessages (init)"
797+ RemoteEndPointValid ep ->
798+ return . Just $ remoteSocket ep
799+ RemoteEndPointClosing _ ep ->
800+ return . Just $ remoteSocket ep
801+ RemoteEndPointClosed ->
802+ return Nothing
803+ RemoteEndPointFailed _ ->
804+ return Nothing
805+
806+ forM_ mSock $ \sock ->
807+ tryIO (go sock) >>= either (prematureExit sock) return
808+ where
809+ -- Dispatch
810+ --
811+ -- If a recv throws an exception this will be caught top-level and
812+ -- 'prematureExit' will be invoked. The same will happen if the remote
813+ -- endpoint is put into a Closed (or Closing) state by a concurrent thread
814+ -- (because a 'send' failed) -- the individual handlers below will throw a
815+ -- user exception which is then caught and handled the same way as an
816+ -- exception thrown by 'recv'.
817+ go :: N.Socket -> IO ()
818+ go sock = do
819+ lcid <- recvInt32 sock :: IO LightweightConnectionId
820+ if lcid >= firstNonReservedLightweightConnectionId
821+ then do
822+ readMessage sock lcid
823+ go sock
824+ else
825+ case tryToEnum (fromIntegral lcid) of
826+ Just CreatedNewConnection -> do
827+ recvInt32 sock >>= createdNewConnection
828+ go sock
829+ Just CloseConnection -> do
830+ recvInt32 sock >>= closeConnection
831+ go sock
832+ Just CloseSocket -> do
833+ didClose <- recvInt32 sock >>= closeSocket sock
834+ unless didClose $ go sock
835+ Nothing ->
836+ throwIO $ userError "Invalid control request"
837+
838+ -- Create a new connection
839+ createdNewConnection :: LightweightConnectionId -> IO ()
840+ createdNewConnection lcid = do
841+ modifyMVar_ theirState $ \st -> do
842+ vst <- case st of
843+ RemoteEndPointInvalid _ ->
844+ relyViolation (ourEndPoint, theirEndPoint)
845+ "handleIncomingMessages:createNewConnection (invalid)"
846+ RemoteEndPointInit _ _ _ ->
847+ relyViolation (ourEndPoint, theirEndPoint)
848+ "handleIncomingMessages:createNewConnection (init)"
849+ RemoteEndPointValid vst ->
850+ return ( (remoteIncoming ^: Set.insert lcid)
851+ $ (remoteMaxIncoming ^= lcid)
852+ vst
853+ )
854+ RemoteEndPointClosing resolved vst -> do
855+ -- If the endpoint is in closing state that means we send a
856+ -- CloseSocket request to the remote endpoint. If the remote
857+ -- endpoint replies that it created a new connection, it either
858+ -- ignored our request or it sent the request before it got ours.
859+ -- Either way, at this point we simply restore the endpoint to
860+ -- RemoteEndPointValid
861+ putMVar resolved ()
862+ return ( (remoteIncoming ^= Set.singleton lcid)
863+ . (remoteMaxIncoming ^= lcid)
864+ $ vst
865+ )
866+ RemoteEndPointFailed err ->
867+ throwIO err
868+ RemoteEndPointClosed ->
869+ relyViolation (ourEndPoint, theirEndPoint)
870+ "createNewConnection (closed)"
871+ return (RemoteEndPointValid vst)
872+ writeChan ourChannel (ConnectionOpened (connId lcid) ReliableOrdered theirAddr)
873+
874+ -- Close a connection
875+ -- It is important that we verify that the connection is in fact open,
876+ -- because otherwise we should not decrement the reference count
877+ closeConnection :: LightweightConnectionId -> IO ()
878+ closeConnection lcid = do
879+ modifyMVar_ theirState $ \st -> case st of
880+ RemoteEndPointInvalid _ ->
881+ relyViolation (ourEndPoint, theirEndPoint) "closeConnection (invalid)"
882+ RemoteEndPointInit _ _ _ ->
883+ relyViolation (ourEndPoint, theirEndPoint) "closeConnection (init)"
884+ RemoteEndPointValid vst -> do
885+ unless (Set.member lcid (vst ^. remoteIncoming)) $
886+ throwIO $ userError "Invalid CloseConnection"
887+ return ( RemoteEndPointValid
888+ . (remoteIncoming ^: Set.delete lcid)
889+ $ vst
890+ )
891+ RemoteEndPointClosing _ _ ->
892+ -- If the remote endpoint is in Closing state, that means that are as
893+ -- far as we are concerned there are no incoming connections. This
894+ -- means that a CloseConnection request at this point is invalid.
895+ throwIO $ userError "Invalid CloseConnection request"
896+ RemoteEndPointFailed err ->
897+ throwIO err
898+ RemoteEndPointClosed ->
899+ relyViolation (ourEndPoint, theirEndPoint) "closeConnection (closed)"
900+ writeChan ourChannel (ConnectionClosed (connId lcid))
901+
902+ -- Close the socket (if we don't have any outgoing connections)
903+ closeSocket :: N.Socket -> LightweightConnectionId -> IO Bool
904+ closeSocket sock lastReceivedId = do
905+ mAct <- modifyMVar theirState $ \st -> do
906+ case st of
907+ RemoteEndPointInvalid _ ->
908+ relyViolation (ourEndPoint, theirEndPoint)
909+ "handleIncomingMessages:closeSocket (invalid)"
910+ RemoteEndPointInit _ _ _ ->
911+ relyViolation (ourEndPoint, theirEndPoint)
912+ "handleIncomingMessages:closeSocket (init)"
913+ RemoteEndPointValid vst -> do
914+ -- We regard a CloseSocket message as an (optimized) way for the
915+ -- remote endpoint to indicate that all its connections to us are
916+ -- now properly closed
917+ forM_ (Set.elems $ vst ^. remoteIncoming) $
918+ writeChan ourChannel . ConnectionClosed . connId
919+ let vst' = remoteIncoming ^= Set.empty $ vst
920+ -- If we still have outgoing connections then we ignore the
921+ -- CloseSocket request (we sent a ConnectionCreated message to the
922+ -- remote endpoint, but it did not receive it before sending the
923+ -- CloseSocket request). Similarly, if lastReceivedId < lastSentId
924+ -- then we sent a ConnectionCreated *AND* a ConnectionClosed
925+ -- message to the remote endpoint, *both of which* it did not yet
926+ -- receive before sending the CloseSocket request.
927+ if vst' ^. remoteOutgoing > 0 || lastReceivedId < lastSentId vst
928+ then
929+ return (RemoteEndPointValid vst', Nothing)
930+ else do
931+ removeRemoteEndPoint (ourEndPoint, theirEndPoint)
932+ -- Attempt to reply (but don't insist)
933+ act <- schedule theirEndPoint $ do
934+ tryIO $ sendOn vst' [ encodeInt32 CloseSocket
935+ , encodeInt32 (vst ^. remoteMaxIncoming)
936+ ]
937+ tryCloseSocket sock
938+ return (RemoteEndPointClosed, Just act)
939+ RemoteEndPointClosing resolved vst -> do
940+ -- Like above, we need to check if there is a ConnectionCreated
941+ -- message that we sent but that the remote endpoint has not yet
942+ -- received. However, since we are in 'closing' state, the only
943+ -- way this may happen is when we sent a ConnectionCreated,
944+ -- ConnectionClosed, and CloseSocket message, none of which have
945+ -- yet been received. We leave the endpoint in closing state in
946+ -- that case.
947+ if lastReceivedId < lastSentId vst
948+ then do
949+ return (RemoteEndPointClosing resolved vst, Nothing)
950+ else do
951+ removeRemoteEndPoint (ourEndPoint, theirEndPoint)
952+ act <- schedule theirEndPoint $ tryCloseSocket sock
953+ putMVar resolved ()
954+ return (RemoteEndPointClosed, Just act)
955+ RemoteEndPointFailed err ->
956+ throwIO err
957+ RemoteEndPointClosed ->
958+ relyViolation (ourEndPoint, theirEndPoint)
959+ "handleIncomingMessages:closeSocket (closed)"
960+ case mAct of
961+ Nothing -> return False
962+ Just act -> do
963+ runScheduledAction (ourEndPoint, theirEndPoint) act
964+ return True
965+
966+ -- Read a message and output it on the endPoint's channel. By rights we
967+ -- should verify that the connection ID is valid, but this is unnecessary
968+ -- overhead
969+ readMessage :: N.Socket -> LightweightConnectionId -> IO ()
970+ readMessage sock lcid =
971+ recvWithLength sock >>= writeChan ourChannel . Received (connId lcid)
972+
973+ -- Arguments
974+ ourChannel = localChannel ourEndPoint
975+ theirState = remoteState theirEndPoint
976+ theirAddr = remoteAddress theirEndPoint
977+
978+ -- Deal with a premature exit
979+ prematureExit :: N.Socket -> IOException -> IO ()
980+ prematureExit sock err = do
981+ tryCloseSocket sock
982+ modifyMVar_ theirState $ \st ->
983+ case st of
984+ RemoteEndPointInvalid _ ->
985+ relyViolation (ourEndPoint, theirEndPoint)
986+ "handleIncomingMessages:prematureExit"
987+ RemoteEndPointInit _ _ _ ->
988+ relyViolation (ourEndPoint, theirEndPoint)
989+ "handleIncomingMessages:prematureExit"
990+ RemoteEndPointValid _ -> do
991+ let code = EventConnectionLost (remoteAddress theirEndPoint)
992+ writeChan ourChannel . ErrorEvent $ TransportError code (show err)
993+ return (RemoteEndPointFailed err)
994+ RemoteEndPointClosing resolved _ -> do
995+ putMVar resolved ()
996+ return (RemoteEndPointFailed err)
997+ RemoteEndPointClosed ->
998+ relyViolation (ourEndPoint, theirEndPoint)
999+ "handleIncomingMessages:prematureExit"
1000+ RemoteEndPointFailed err' ->
1001+ return (RemoteEndPointFailed err')
1002+
1003+ -- Construct a connection ID
1004+ connId :: LightweightConnectionId -> ConnectionId
1005+ connId = createConnectionId (remoteId theirEndPoint)
1006+
1007+ -- The ID of the last connection _we_ created (or 0 for none)
1008+ lastSentId :: ValidRemoteEndPointState -> LightweightConnectionId
1009+ lastSentId vst =
1010+ if vst ^. remoteNextConnOutId == firstNonReservedLightweightConnectionId
1011+ then 0
1012+ else (vst ^. remoteNextConnOutId) - 1
1013+
1014+--------------------------------------------------------------------------------
1015+-- Uninterruptable auxiliary functions --
1016+-- --
1017+-- All these functions assume they are running in a thread which will never --
1018+-- be killed.
1019+--------------------------------------------------------------------------------
1020+
1021+-- | Create a connection to a remote endpoint
1022+--
1023+-- If the remote endpoint is in 'RemoteEndPointClosing' state then we will
1024+-- block until that is resolved.
1025+--
1026+-- May throw a TransportError ConnectErrorCode exception.
1027+createConnectionTo :: TCPParameters
1028+ -> LocalEndPoint
1029+ -> EndPointAddress
1030+ -> ConnectHints
1031+ -> IO (RemoteEndPoint, LightweightConnectionId)
1032+createConnectionTo params ourEndPoint theirAddress hints = go
1033+ where
1034+ go = do
1035+ (theirEndPoint, isNew) <- mapIOException connectFailed $
1036+ findRemoteEndPoint ourEndPoint theirAddress RequestedByUs
1037+
1038+ if isNew
1039+ then do
1040+ forkIO . handle absorbAllExceptions $
1041+ setupRemoteEndPoint params (ourEndPoint, theirEndPoint) hints
1042+ go
1043+ else do
1044+ -- 'findRemoteEndPoint' will have increased 'remoteOutgoing'
1045+ mapIOException connectFailed $ do
1046+ act <- modifyMVar (remoteState theirEndPoint) $ \st -> case st of
1047+ RemoteEndPointValid vst -> do
1048+ let connId = vst ^. remoteNextConnOutId
1049+ act <- schedule theirEndPoint $ do
1050+ sendOn vst [encodeInt32 CreatedNewConnection, encodeInt32 connId]
1051+ return connId
1052+ return ( RemoteEndPointValid
1053+ $ remoteNextConnOutId ^= connId + 1
1054+ $ vst
1055+ , act
1056+ )
1057+ -- Error cases
1058+ RemoteEndPointInvalid err ->
1059+ throwIO err
1060+ RemoteEndPointFailed err ->
1061+ throwIO err
1062+ -- Algorithmic errors
1063+ _ ->
1064+ relyViolation (ourEndPoint, theirEndPoint) "createConnectionTo"
1065+ -- TODO: deal with exception case?
1066+ connId <- runScheduledAction (ourEndPoint, theirEndPoint) act
1067+ return (theirEndPoint, connId)
1068+
1069+
1070+ connectFailed :: IOException -> TransportError ConnectErrorCode
1071+ connectFailed = TransportError ConnectFailed . show
1072+
1073+ absorbAllExceptions :: SomeException -> IO ()
1074+ absorbAllExceptions _ex =
1075+ return ()
1076+
1077+-- | Set up a remote endpoint
1078+setupRemoteEndPoint :: TCPParameters -> EndPointPair -> ConnectHints -> IO ()
1079+setupRemoteEndPoint params (ourEndPoint, theirEndPoint) hints = do
1080+ result <- socketToEndPoint ourAddress
1081+ theirAddress
1082+ (tcpReuseClientAddr params)
1083+ (connectTimeout hints)
1084+ didAccept <- case result of
1085+ Right (sock, ConnectionRequestAccepted) -> do
1086+ sendLock <- newMVar ()
1087+ let vst = ValidRemoteEndPointState
1088+ { remoteSocket = sock
1089+ , remoteSendLock = sendLock
1090+ , _remoteOutgoing = 0
1091+ , _remoteIncoming = Set.empty
1092+ , _remoteMaxIncoming = 0
1093+ , _remoteNextConnOutId = firstNonReservedLightweightConnectionId
1094+ }
1095+ resolveInit (ourEndPoint, theirEndPoint) (RemoteEndPointValid vst)
1096+ return True
1097+ Right (sock, ConnectionRequestInvalid) -> do
1098+ let err = invalidAddress "setupRemoteEndPoint: Invalid endpoint"
1099+ resolveInit (ourEndPoint, theirEndPoint) (RemoteEndPointInvalid err)
1100+ tryCloseSocket sock
1101+ return False
1102+ Right (sock, ConnectionRequestCrossed) -> do
1103+ withMVar (remoteState theirEndPoint) $ \st -> case st of
1104+ RemoteEndPointInit _ crossed _ ->
1105+ putMVar crossed ()
1106+ RemoteEndPointFailed ex ->
1107+ throwIO ex
1108+ _ ->
1109+ relyViolation (ourEndPoint, theirEndPoint) "setupRemoteEndPoint: Crossed"
1110+ tryCloseSocket sock
1111+ return False
1112+ Left err -> do
1113+ resolveInit (ourEndPoint, theirEndPoint) (RemoteEndPointInvalid err)
1114+ return False
1115+
1116+ when didAccept $ handleIncomingMessages (ourEndPoint, theirEndPoint)
1117+ where
1118+ ourAddress = localAddress ourEndPoint
1119+ theirAddress = remoteAddress theirEndPoint
1120+ invalidAddress = TransportError ConnectNotFound
1121+
1122+-- | Send a CloseSocket request if the remote endpoint is unused
1123+closeIfUnused :: EndPointPair -> IO ()
1124+closeIfUnused (ourEndPoint, theirEndPoint) = do
1125+ mAct <- modifyMVar (remoteState theirEndPoint) $ \st -> case st of
1126+ RemoteEndPointValid vst ->
1127+ if vst ^. remoteOutgoing == 0 && Set.null (vst ^. remoteIncoming)
1128+ then do
1129+ resolved <- newEmptyMVar
1130+ act <- schedule theirEndPoint $
1131+ sendOn vst [ encodeInt32 CloseSocket
1132+ , encodeInt32 (vst ^. remoteMaxIncoming)
1133+ ]
1134+ return (RemoteEndPointClosing resolved vst, Just act)
1135+ else
1136+ return (RemoteEndPointValid vst, Nothing)
1137+ _ ->
1138+ return (st, Nothing)
1139+ forM_ mAct $ runScheduledAction (ourEndPoint, theirEndPoint)
1140+
1141+-- | Reset a remote endpoint if it is in Invalid mode
1142+--
1143+-- If the remote endpoint is currently in broken state, and
1144+--
1145+-- - a user calls the API function 'connect', or and the remote endpoint is
1146+-- - an inbound connection request comes in from this remote address
1147+--
1148+-- we remove the remote endpoint first.
1149+--
1150+-- Throws a TransportError ConnectFailed exception if the local endpoint is
1151+-- closed.
1152+resetIfBroken :: LocalEndPoint -> EndPointAddress -> IO ()
1153+resetIfBroken ourEndPoint theirAddress = do
1154+ mTheirEndPoint <- withMVar (localState ourEndPoint) $ \st -> case st of
1155+ LocalEndPointValid vst ->
1156+ return (vst ^. localConnectionTo theirAddress)
1157+ LocalEndPointClosed ->
1158+ throwIO $ TransportError ConnectFailed "Endpoint closed"
1159+ forM_ mTheirEndPoint $ \theirEndPoint ->
1160+ withMVar (remoteState theirEndPoint) $ \st -> case st of
1161+ RemoteEndPointInvalid _ ->
1162+ removeRemoteEndPoint (ourEndPoint, theirEndPoint)
1163+ RemoteEndPointFailed _ ->
1164+ removeRemoteEndPoint (ourEndPoint, theirEndPoint)
1165+ _ ->
1166+ return ()
1167+
1168+-- | Special case of 'apiConnect': connect an endpoint to itself
1169+--
1170+-- May throw a TransportError ConnectErrorCode (if the local endpoint is closed)
1171+connectToSelf :: LocalEndPoint
1172+ -> IO Connection
1173+connectToSelf ourEndPoint = do
1174+ connAlive <- newIORef True -- Protected by the local endpoint lock
1175+ lconnId <- mapIOException connectFailed $ getLocalNextConnOutId ourEndPoint
1176+ let connId = createConnectionId heavyweightSelfConnectionId lconnId
1177+ writeChan ourChan $
1178+ ConnectionOpened connId ReliableOrdered (localAddress ourEndPoint)
1179+ return Connection
1180+ { send = selfSend connAlive connId
1181+ , close = selfClose connAlive connId
1182+ }
1183+ where
1184+ selfSend :: IORef Bool
1185+ -> ConnectionId
1186+ -> [ByteString]
1187+ -> IO (Either (TransportError SendErrorCode) ())
1188+ selfSend connAlive connId msg =
1189+ try . withMVar ourState $ \st -> case st of
1190+ LocalEndPointValid _ -> do
1191+ alive <- readIORef connAlive
1192+ if alive
1193+ then writeChan ourChan (Received connId msg)
1194+ else throwIO $ TransportError SendClosed "Connection closed"
1195+ LocalEndPointClosed ->
1196+ throwIO $ TransportError SendFailed "Endpoint closed"
1197+
1198+ selfClose :: IORef Bool -> ConnectionId -> IO ()
1199+ selfClose connAlive connId =
1200+ withMVar ourState $ \st -> case st of
1201+ LocalEndPointValid _ -> do
1202+ alive <- readIORef connAlive
1203+ when alive $ do
1204+ writeChan ourChan (ConnectionClosed connId)
1205+ writeIORef connAlive False
1206+ LocalEndPointClosed ->
1207+ return ()
1208+
1209+ ourChan = localChannel ourEndPoint
1210+ ourState = localState ourEndPoint
1211+ connectFailed = TransportError ConnectFailed . show
1212+
1213+-- | Resolve an endpoint currently in 'Init' state
1214+resolveInit :: EndPointPair -> RemoteState -> IO ()
1215+resolveInit (ourEndPoint, theirEndPoint) newState =
1216+ modifyMVar_ (remoteState theirEndPoint) $ \st -> case st of
1217+ RemoteEndPointInit resolved _ _ -> do
1218+ putMVar resolved ()
1219+ case newState of
1220+ RemoteEndPointClosed ->
1221+ removeRemoteEndPoint (ourEndPoint, theirEndPoint)
1222+ _ ->
1223+ return ()
1224+ return newState
1225+ RemoteEndPointFailed ex ->
1226+ throwIO ex
1227+ _ ->
1228+ relyViolation (ourEndPoint, theirEndPoint) "resolveInit"
1229+
1230+-- | Get the next outgoing self-connection ID
1231+--
1232+-- Throws an IO exception when the endpoint is closed.
1233+getLocalNextConnOutId :: LocalEndPoint -> IO LightweightConnectionId
1234+getLocalNextConnOutId ourEndpoint =
1235+ modifyMVar (localState ourEndpoint) $ \st -> case st of
1236+ LocalEndPointValid vst -> do
1237+ let connId = vst ^. localNextConnOutId
1238+ return ( LocalEndPointValid
1239+ . (localNextConnOutId ^= connId + 1)
1240+ $ vst
1241+ , connId)
1242+ LocalEndPointClosed ->
1243+ throwIO $ userError "Local endpoint closed"
1244+
1245+-- | Create a new local endpoint
1246+--
1247+-- May throw a TransportError NewEndPointErrorCode exception if the transport
1248+-- is closed.
1249+createLocalEndPoint :: TCPTransport -> IO LocalEndPoint
1250+createLocalEndPoint transport = do
1251+ chan <- newChan
1252+ state <- newMVar . LocalEndPointValid $ ValidLocalEndPointState
1253+ { _localNextConnOutId = firstNonReservedLightweightConnectionId
1254+ , _localConnections = Map.empty
1255+ , _nextConnInId = firstNonReservedHeavyweightConnectionId
1256+ }
1257+ modifyMVar (transportState transport) $ \st -> case st of
1258+ TransportValid vst -> do
1259+ let ix = vst ^. nextEndPointId
1260+ let addr = encodeEndPointAddress (transportHost transport)
1261+ (transportPort transport)
1262+ ix
1263+ let localEndPoint = LocalEndPoint { localAddress = addr
1264+ , localChannel = chan
1265+ , localState = state
1266+ }
1267+ return ( TransportValid
1268+ . (localEndPointAt addr ^= Just localEndPoint)
1269+ . (nextEndPointId ^= ix + 1)
1270+ $ vst
1271+ , localEndPoint
1272+ )
1273+ TransportClosed ->
1274+ throwIO (TransportError NewEndPointFailed "Transport closed")
1275+
1276+
1277+-- | Remove reference to a remote endpoint from a local endpoint
1278+--
1279+-- If the local endpoint is closed, do nothing
1280+removeRemoteEndPoint :: EndPointPair -> IO ()
1281+removeRemoteEndPoint (ourEndPoint, theirEndPoint) =
1282+ modifyMVar_ ourState $ \st -> case st of
1283+ LocalEndPointValid vst ->
1284+ case vst ^. localConnectionTo theirAddress of
1285+ Nothing ->
1286+ return st
1287+ Just remoteEndPoint' ->
1288+ if remoteId remoteEndPoint' == remoteId theirEndPoint
1289+ then return
1290+ ( LocalEndPointValid
1291+ . (localConnectionTo (remoteAddress theirEndPoint) ^= Nothing)
1292+ $ vst
1293+ )
1294+ else return st
1295+ LocalEndPointClosed ->
1296+ return LocalEndPointClosed
1297+ where
1298+ ourState = localState ourEndPoint
1299+ theirAddress = remoteAddress theirEndPoint
1300+
1301+-- | Remove reference to a local endpoint from the transport state
1302+--
1303+-- Does nothing if the transport is closed
1304+removeLocalEndPoint :: TCPTransport -> LocalEndPoint -> IO ()
1305+removeLocalEndPoint transport ourEndPoint =
1306+ modifyMVar_ (transportState transport) $ \st -> case st of
1307+ TransportValid vst ->
1308+ return ( TransportValid
1309+ . (localEndPointAt (localAddress ourEndPoint) ^= Nothing)
1310+ $ vst
1311+ )
1312+ TransportClosed ->
1313+ return TransportClosed
1314+
1315+-- | Find a remote endpoint. If the remote endpoint does not yet exist we
1316+-- create it in Init state. Returns if the endpoint was new.
1317+findRemoteEndPoint
1318+ :: LocalEndPoint
1319+ -> EndPointAddress
1320+ -> RequestedBy
1321+ -> IO (RemoteEndPoint, Bool)
1322+findRemoteEndPoint ourEndPoint theirAddress findOrigin = go
1323+ where
1324+ go = do
1325+ (theirEndPoint, isNew) <- modifyMVar ourState $ \st -> case st of
1326+ LocalEndPointValid vst -> case vst ^. localConnectionTo theirAddress of
1327+ Just theirEndPoint ->
1328+ return (st, (theirEndPoint, False))
1329+ Nothing -> do
1330+ resolved <- newEmptyMVar
1331+ crossed <- newEmptyMVar
1332+ theirState <- newMVar (RemoteEndPointInit resolved crossed findOrigin)
1333+ scheduled <- newChan
1334+ let theirEndPoint = RemoteEndPoint
1335+ { remoteAddress = theirAddress
1336+ , remoteState = theirState
1337+ , remoteId = vst ^. nextConnInId
1338+ , remoteScheduled = scheduled
1339+ }
1340+ return ( LocalEndPointValid
1341+ . (localConnectionTo theirAddress ^= Just theirEndPoint)
1342+ . (nextConnInId ^: (+ 1))
1343+ $ vst
1344+ , (theirEndPoint, True)
1345+ )
1346+ LocalEndPointClosed ->
1347+ throwIO $ userError "Local endpoint closed"
1348+
1349+ if isNew
1350+ then
1351+ return (theirEndPoint, True)
1352+ else do
1353+ let theirState = remoteState theirEndPoint
1354+ snapshot <- modifyMVar theirState $ \st -> case st of
1355+ RemoteEndPointValid vst ->
1356+ case findOrigin of
1357+ RequestedByUs -> do
1358+ let st' = RemoteEndPointValid
1359+ . (remoteOutgoing ^: (+ 1))
1360+ $ vst
1361+ return (st', st')
1362+ RequestedByThem ->
1363+ return (st, st)
1364+ _ ->
1365+ return (st, st)
1366+ -- The snapshot may no longer be up to date at this point, but if we
1367+ -- increased the refcount then it can only either be Valid or Failed
1368+ -- (after an explicit call to 'closeEndPoint' or 'closeTransport')
1369+ case snapshot of
1370+ RemoteEndPointInvalid err ->
1371+ throwIO err
1372+ RemoteEndPointInit resolved crossed initOrigin ->
1373+ case (findOrigin, initOrigin) of
1374+ (RequestedByUs, RequestedByUs) ->
1375+ readMVar resolved >> go
1376+ (RequestedByUs, RequestedByThem) ->
1377+ readMVar resolved >> go
1378+ (RequestedByThem, RequestedByUs) ->
1379+ if ourAddress > theirAddress
1380+ then do
1381+ -- Wait for the Crossed message
1382+ readMVar crossed
1383+ return (theirEndPoint, True)
1384+ else
1385+ return (theirEndPoint, False)
1386+ (RequestedByThem, RequestedByThem) ->
1387+ throwIO $ userError "Already connected"
1388+ RemoteEndPointValid _ ->
1389+ -- We assume that the request crossed if we find the endpoint in
1390+ -- Valid state. It is possible that this is really an invalid
1391+ -- request, but only in the case of a broken client (we don't
1392+ -- maintain enough history to be able to tell the difference).
1393+ return (theirEndPoint, False)
1394+ RemoteEndPointClosing resolved _ ->
1395+ readMVar resolved >> go
1396+ RemoteEndPointClosed ->
1397+ go
1398+ RemoteEndPointFailed err ->
1399+ throwIO err
1400+
1401+ ourState = localState ourEndPoint
1402+ ourAddress = localAddress ourEndPoint
1403+
1404+-- | Send a payload over a heavyweight connection (thread safe)
1405+sendOn :: ValidRemoteEndPointState -> [ByteString] -> IO ()
1406+sendOn vst bs = withMVar (remoteSendLock vst) $ \() ->
1407+ sendMany (remoteSocket vst) bs
1408+
1409+--------------------------------------------------------------------------------
1410+-- Scheduling actions --
1411+--------------------------------------------------------------------------------
1412+
1413+-- | See 'schedule'/'runScheduledAction'
1414+type Action a = MVar (Either SomeException a)
1415+
1416+-- | Schedule an action to be executed (see also 'runScheduledAction')
1417+schedule :: RemoteEndPoint -> IO a -> IO (Action a)
1418+schedule theirEndPoint act = do
1419+ mvar <- newEmptyMVar
1420+ writeChan (remoteScheduled theirEndPoint) $
1421+ catch (act >>= putMVar mvar . Right) (putMVar mvar . Left)
1422+ return mvar
1423+
1424+-- | Run a scheduled action. Every call to 'schedule' should be paired with a
1425+-- call to 'runScheduledAction' so that every scheduled action is run. Note
1426+-- however that the there is no guarantee that in
1427+--
1428+-- > do act <- schedule p
1429+-- > runScheduledAction
1430+--
1431+-- 'runScheduledAction' will run @p@ (it might run some other scheduled action).
1432+-- However, it will then wait until @p@ is executed (by this call to
1433+-- 'runScheduledAction' or by another).
1434+runScheduledAction :: EndPointPair -> Action a -> IO a
1435+runScheduledAction (ourEndPoint, theirEndPoint) mvar = do
1436+ join $ readChan (remoteScheduled theirEndPoint)
1437+ ma <- readMVar mvar
1438+ case ma of
1439+ Right a -> return a
1440+ Left e -> do
1441+ forM_ (fromException e) $ \ioe ->
1442+ modifyMVar_ (remoteState theirEndPoint) $ \st ->
1443+ case st of
1444+ RemoteEndPointValid vst -> handleIOException ioe vst
1445+ _ -> return (RemoteEndPointFailed ioe)
1446+ throwIO e
1447+ where
1448+ handleIOException :: IOException
1449+ -> ValidRemoteEndPointState
1450+ -> IO RemoteState
1451+ handleIOException ex vst = do
1452+ tryCloseSocket (remoteSocket vst)
1453+ let code = EventConnectionLost (remoteAddress theirEndPoint)
1454+ err = TransportError code (show ex)
1455+ writeChan (localChannel ourEndPoint) $ ErrorEvent err
1456+ return (RemoteEndPointFailed ex)
1457+
1458+--------------------------------------------------------------------------------
1459+-- "Stateless" (MVar free) functions --
1460+--------------------------------------------------------------------------------
1461+
1462+-- | Establish a connection to a remote endpoint
1463+--
1464+-- Maybe throw a TransportError
1465+socketToEndPoint :: EndPointAddress -- ^ Our address
1466+ -> EndPointAddress -- ^ Their address
1467+ -> Bool -- ^ Use SO_REUSEADDR?
1468+ -> Maybe Int -- ^ Timeout for connect
1469+ -> IO (Either (TransportError ConnectErrorCode)
1470+ (N.Socket, ConnectionRequestResponse))
1471+socketToEndPoint (EndPointAddress ourAddress) theirAddress reuseAddr timeout =
1472+ try $ do
1473+ (host, port, theirEndPointId) <- case decodeEndPointAddress theirAddress of
1474+ Nothing -> throwIO (failed . userError $ "Could not parse")
1475+ Just dec -> return dec
1476+ addr:_ <- mapIOException invalidAddress $
1477+ N.getAddrInfo Nothing (Just host) (Just port)
1478+ bracketOnError (createSocket addr) tryCloseSocket $ \sock -> do
1479+ when reuseAddr $
1480+ mapIOException failed $ N.setSocketOption sock N.ReuseAddr 1
1481+ mapIOException invalidAddress $
1482+ timeoutMaybe timeout timeoutError $
1483+ N.connect sock (N.addrAddress addr)
1484+ response <- mapIOException failed $ do
1485+ sendMany sock (encodeInt32 theirEndPointId : prependLength [ourAddress])
1486+ recvInt32 sock
1487+ case tryToEnum response of
1488+ Nothing -> throwIO (failed . userError $ "Unexpected response")
1489+ Just r -> return (sock, r)
1490+ where
1491+ createSocket :: N.AddrInfo -> IO N.Socket
1492+ createSocket addr = mapIOException insufficientResources $
1493+ N.socket (N.addrFamily addr) N.Stream N.defaultProtocol
1494+
1495+ invalidAddress = TransportError ConnectNotFound . show
1496+ insufficientResources = TransportError ConnectInsufficientResources . show
1497+ failed = TransportError ConnectFailed . show
1498+ timeoutError = TransportError ConnectTimeout "Timed out"
1499+
1500+-- | Encode end point address
1501+encodeEndPointAddress :: N.HostName
1502+ -> N.ServiceName
1503+ -> EndPointId
1504+ -> EndPointAddress
1505+encodeEndPointAddress host port ix = EndPointAddress . BSC.pack $
1506+ host ++ ":" ++ port ++ ":" ++ show ix
1507+
1508+-- | Decode end point address
1509+decodeEndPointAddress :: EndPointAddress
1510+ -> Maybe (N.HostName, N.ServiceName, EndPointId)
1511+decodeEndPointAddress (EndPointAddress bs) =
1512+ case splitMaxFromEnd (== ':') 2 $ BSC.unpack bs of
1513+ [host, port, endPointIdStr] ->
1514+ case reads endPointIdStr of
1515+ [(endPointId, "")] -> Just (host, port, endPointId)
1516+ _ -> Nothing
1517+ _ ->
1518+ Nothing
1519+
1520+-- | Construct a ConnectionId
1521+createConnectionId :: HeavyweightConnectionId
1522+ -> LightweightConnectionId
1523+ -> ConnectionId
1524+createConnectionId hcid lcid =
1525+ (fromIntegral hcid `shiftL` 32) .|. fromIntegral lcid
1526+
1527+-- | @spltiMaxFromEnd p n xs@ splits list @xs@ at elements matching @p@,
1528+-- returning at most @p@ segments -- counting from the /end/
1529+--
1530+-- > splitMaxFromEnd (== ':') 2 "ab:cd:ef:gh" == ["ab:cd", "ef", "gh"]
1531+splitMaxFromEnd :: (a -> Bool) -> Int -> [a] -> [[a]]
1532+splitMaxFromEnd p = \n -> go [[]] n . reverse
1533+ where
1534+ -- go :: [[a]] -> Int -> [a] -> [[a]]
1535+ go accs _ [] = accs
1536+ go ([] : accs) 0 xs = reverse xs : accs
1537+ go (acc : accs) n (x:xs) =
1538+ if p x then go ([] : acc : accs) (n - 1) xs
1539+ else go ((x : acc) : accs) n xs
1540+ go _ _ _ = error "Bug in splitMaxFromEnd"
1541+
1542+--------------------------------------------------------------------------------
1543+-- Functions from TransportInternals --
1544+--------------------------------------------------------------------------------
1545+
1546+-- Find a socket between two endpoints
1547+--
1548+-- Throws an IO exception if the socket could not be found.
1549+internalSocketBetween :: TCPTransport -- ^ Transport
1550+ -> EndPointAddress -- ^ Local endpoint
1551+ -> EndPointAddress -- ^ Remote endpoint
1552+ -> IO N.Socket
1553+internalSocketBetween transport ourAddress theirAddress = do
1554+ ourEndPoint <- withMVar (transportState transport) $ \st -> case st of
1555+ TransportClosed ->
1556+ throwIO $ userError "Transport closed"
1557+ TransportValid vst ->
1558+ case vst ^. localEndPointAt ourAddress of
1559+ Nothing -> throwIO $ userError "Local endpoint not found"
1560+ Just ep -> return ep
1561+ theirEndPoint <- withMVar (localState ourEndPoint) $ \st -> case st of
1562+ LocalEndPointClosed ->
1563+ throwIO $ userError "Local endpoint closed"
1564+ LocalEndPointValid vst ->
1565+ case vst ^. localConnectionTo theirAddress of
1566+ Nothing -> throwIO $ userError "Remote endpoint not found"
1567+ Just ep -> return ep
1568+ withMVar (remoteState theirEndPoint) $ \st -> case st of
1569+ RemoteEndPointInit _ _ _ ->
1570+ throwIO $ userError "Remote endpoint not yet initialized"
1571+ RemoteEndPointValid vst ->
1572+ return $ remoteSocket vst
1573+ RemoteEndPointClosing _ vst ->
1574+ return $ remoteSocket vst
1575+ RemoteEndPointClosed ->
1576+ throwIO $ userError "Remote endpoint closed"
1577+ RemoteEndPointInvalid err ->
1578+ throwIO err
1579+ RemoteEndPointFailed err ->
1580+ throwIO err
1581+
1582+--------------------------------------------------------------------------------
1583+-- Constants --
1584+--------------------------------------------------------------------------------
1585+
1586+-- | We reserve a bunch of connection IDs for control messages
1587+firstNonReservedLightweightConnectionId :: LightweightConnectionId
1588+firstNonReservedLightweightConnectionId = 1024
1589+
1590+-- | Self-connection
1591+heavyweightSelfConnectionId :: HeavyweightConnectionId
1592+heavyweightSelfConnectionId = 0
1593+
1594+-- | We reserve some connection IDs for special heavyweight connections
1595+firstNonReservedHeavyweightConnectionId :: HeavyweightConnectionId
1596+firstNonReservedHeavyweightConnectionId = 1
1597+
1598+--------------------------------------------------------------------------------
1599+-- Accessor definitions --
1600+--------------------------------------------------------------------------------
1601+
1602+localEndPoints :: Accessor ValidTransportState (Map EndPointAddress LocalEndPoint)
1603+localEndPoints = accessor _localEndPoints (\es st -> st { _localEndPoints = es })
1604+
1605+nextEndPointId :: Accessor ValidTransportState EndPointId
1606+nextEndPointId = accessor _nextEndPointId (\eid st -> st { _nextEndPointId = eid })
1607+
1608+localNextConnOutId :: Accessor ValidLocalEndPointState LightweightConnectionId
1609+localNextConnOutId = accessor _localNextConnOutId (\cix st -> st { _localNextConnOutId = cix })
1610+
1611+localConnections :: Accessor ValidLocalEndPointState (Map EndPointAddress RemoteEndPoint)
1612+localConnections = accessor _localConnections (\es st -> st { _localConnections = es })
1613+
1614+nextConnInId :: Accessor ValidLocalEndPointState HeavyweightConnectionId
1615+nextConnInId = accessor _nextConnInId (\rid st -> st { _nextConnInId = rid })
1616+
1617+remoteOutgoing :: Accessor ValidRemoteEndPointState Int
1618+remoteOutgoing = accessor _remoteOutgoing (\cs conn -> conn { _remoteOutgoing = cs })
1619+
1620+remoteIncoming :: Accessor ValidRemoteEndPointState (Set LightweightConnectionId)
1621+remoteIncoming = accessor _remoteIncoming (\cs conn -> conn { _remoteIncoming = cs })
1622+
1623+remoteMaxIncoming :: Accessor ValidRemoteEndPointState LightweightConnectionId
1624+remoteMaxIncoming = accessor _remoteMaxIncoming (\lcid st -> st { _remoteMaxIncoming = lcid })
1625+
1626+remoteNextConnOutId :: Accessor ValidRemoteEndPointState LightweightConnectionId
1627+remoteNextConnOutId = accessor _remoteNextConnOutId (\cix st -> st { _remoteNextConnOutId = cix })
1628+
1629+localEndPointAt :: EndPointAddress -> Accessor ValidTransportState (Maybe LocalEndPoint)
1630+localEndPointAt addr = localEndPoints >>> DAC.mapMaybe addr
1631+
1632+localConnectionTo :: EndPointAddress -> Accessor ValidLocalEndPointState (Maybe RemoteEndPoint)
1633+localConnectionTo addr = localConnections >>> DAC.mapMaybe addr
1634+
1635+-------------------------------------------------------------------------------
1636+-- Debugging --
1637+-------------------------------------------------------------------------------
1638+
1639+relyViolation :: EndPointPair -> String -> IO a
1640+relyViolation (ourEndPoint, theirEndPoint) str = do
1641+ elog (ourEndPoint, theirEndPoint) (str ++ " RELY violation")
1642+ fail (str ++ " RELY violation")
1643+
1644+elog :: EndPointPair -> String -> IO ()
1645+elog (ourEndPoint, theirEndPoint) msg = do
1646+ tid <- myThreadId
1647+ putStrLn $ show (localAddress ourEndPoint)
1648+ ++ "/" ++ show (remoteAddress theirEndPoint)
1649+ ++ "(" ++ show (remoteId theirEndPoint) ++ ")"
1650+ ++ "/" ++ show tid
1651+ ++ ": " ++ msg
network-transport-tcp/src/Network/Transport/TCP/Internal.hsnew file+133
@@ -0,0 +1,133 @@
1+-- | Utility functions for TCP sockets
2+module Network.Transport.TCP.Internal
3+ ( forkServer
4+ , recvWithLength
5+ , recvExact
6+ , recvInt32
7+ , tryCloseSocket
8+ ) where
9+
10+#if ! MIN_VERSION_base(4,6,0)
11+import Prelude hiding (catch)
12+#endif
13+
14+import Network.Transport.Internal (decodeInt32, void, tryIO, forkIOWithUnmask)
15+
16+#ifdef USE_MOCK_NETWORK
17+import qualified Network.Transport.TCP.Mock.Socket as N
18+#else
19+import qualified Network.Socket as N
20+#endif
21+ ( HostName
22+ , ServiceName
23+ , Socket
24+ , SocketType(Stream)
25+ , SocketOption(ReuseAddr)
26+ , getAddrInfo
27+ , defaultHints
28+ , socket
29+ , bindSocket
30+ , listen
31+ , addrFamily
32+ , addrAddress
33+ , defaultProtocol
34+ , setSocketOption
35+ , accept
36+ , sClose
37+ )
38+
39+#ifdef USE_MOCK_NETWORK
40+import qualified Network.Transport.TCP.Mock.Socket.ByteString as NBS (recv)
41+#else
42+import qualified Network.Socket.ByteString as NBS (recv)
43+#endif
44+
45+import Control.Concurrent (ThreadId)
46+import Control.Monad (forever, when)
47+import Control.Exception (SomeException, catch, bracketOnError, throwIO, mask_)
48+import Control.Applicative ((<$>))
49+import Data.ByteString (ByteString)
50+import qualified Data.ByteString as BS (length, concat, null)
51+import Data.Int (Int32)
52+import Data.ByteString.Lazy.Internal (smallChunkSize)
53+
54+-- | Start a server at the specified address.
55+--
56+-- This sets up a server socket for the specified host and port. Exceptions
57+-- thrown during setup are not caught.
58+--
59+-- Once the socket is created we spawn a new thread which repeatedly accepts
60+-- incoming connections and executes the given request handler. If any
61+-- exception occurs the thread terminates and calls the terminationHandler.
62+-- This exception may occur because of a call to 'N.accept', because the thread
63+-- was explicitly killed, or because of a synchronous exception thrown by the
64+-- request handler. Typically, you should avoid the last case by catching any
65+-- relevant exceptions in the request handler.
66+--
67+-- The request handler should spawn threads to handle each individual request
68+-- or the server will block. Once a thread has been spawned it will be the
69+-- responsibility of the new thread to close the socket when an exception
70+-- occurs.
71+forkServer :: N.HostName -- ^ Host
72+ -> N.ServiceName -- ^ Port
73+ -> Int -- ^ Backlog (maximum number of queued connections)
74+ -> Bool -- ^ Set ReuseAddr option?
75+ -> (SomeException -> IO ()) -- ^ Termination handler
76+ -> (N.Socket -> IO ()) -- ^ Request handler
77+ -> IO ThreadId
78+forkServer host port backlog reuseAddr terminationHandler requestHandler = do
79+ -- Resolve the specified address. By specification, getAddrInfo will never
80+ -- return an empty list (but will throw an exception instead) and will return
81+ -- the "best" address first, whatever that means
82+ addr:_ <- N.getAddrInfo (Just N.defaultHints) (Just host) (Just port)
83+ bracketOnError (N.socket (N.addrFamily addr) N.Stream N.defaultProtocol)
84+ tryCloseSocket $ \sock -> do
85+ when reuseAddr $ N.setSocketOption sock N.ReuseAddr 1
86+ N.bindSocket sock (N.addrAddress addr)
87+ N.listen sock backlog
88+ -- We start listening for incoming requests in a separate thread. When
89+ -- that thread is killed, we close the server socket and the termination
90+ -- handler. We have to make sure that the exception handler is installed
91+ -- /before/ any asynchronous exception occurs. So we mask_, then fork
92+ -- (the child thread inherits the masked state from the parent), then
93+ -- unmask only inside the catch.
94+ mask_ $ forkIOWithUnmask $ \unmask ->
95+ catch (unmask (forever $ acceptRequest sock)) $ \ex -> do
96+ tryCloseSocket sock
97+ terminationHandler ex
98+ where
99+ acceptRequest :: N.Socket -> IO ()
100+ acceptRequest sock = bracketOnError (N.accept sock)
101+ (tryCloseSocket . fst)
102+ (requestHandler . fst)
103+
104+-- | Read a length and then a payload of that length
105+recvWithLength :: N.Socket -> IO [ByteString]
106+recvWithLength sock = recvInt32 sock >>= recvExact sock
107+
108+-- | Receive a 32-bit integer
109+recvInt32 :: Num a => N.Socket -> IO a
110+recvInt32 sock = decodeInt32 . BS.concat <$> recvExact sock 4
111+
112+-- | Close a socket, ignoring I/O exceptions
113+tryCloseSocket :: N.Socket -> IO ()
114+tryCloseSocket sock = void . tryIO $
115+ N.sClose sock
116+
117+-- | Read an exact number of bytes from a socket
118+--
119+-- Throws an I/O exception if the socket closes before the specified
120+-- number of bytes could be read
121+recvExact :: N.Socket -- ^ Socket to read from
122+ -> Int32 -- ^ Number of bytes to read
123+ -> IO [ByteString]
124+recvExact _ len | len < 0 = throwIO (userError "recvExact: Negative length")
125+recvExact sock len = go [] len
126+ where
127+ go :: [ByteString] -> Int32 -> IO [ByteString]
128+ go acc 0 = return (reverse acc)
129+ go acc l = do
130+ bs <- NBS.recv sock (fromIntegral l `min` smallChunkSize)
131+ if BS.null bs
132+ then throwIO (userError "recvExact: Socket closed")
133+ else go (bs : acc) (l - fromIntegral (BS.length bs))
network-transport-tcp/src/Network/Transport/TCP/Mock/Socket.hsnew file+359

This diff is too big to show here.

network-transport-tcp/src/Network/Transport/TCP/Mock/Socket/ByteString.hsnew file+27

This diff is too big to show here.

network-transport-tcp/tests/TestQC.hsnew file+848

This diff is too big to show here.

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

This diff is too big to show here.

network-transport-tests/ChangeLognew file+7

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+22

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+33

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+11

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/Closure/Explicit.hsdeleted−147

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/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/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.