git.iko.soy/distributed-process
on master help sign in
62 files changed+6685−946
since splitsnapshots
unifiedsplit
.gitignore+3−14 .travis.yml+36−15 CONTRIBUTING.md+1 ChangeLog+100 LICENSE+1 Makefile−6 README.md+7−11 REPOS−5 benchmarks/Channels.hs+6−4 benchmarks/Latency.hs+7−4 benchmarks/ProcessRing.hs+7−3 benchmarks/Spawns.hs+8−4 benchmarks/Throughput.hs+8−4 distributed-process-tests/.gitignore+4 distributed-process-tests/CONTRIBUTING.md+1 distributed-process-tests/LICENSE+31 distributed-process-tests/README.md+14 distributed-process-tests/Setup.hs+2 distributed-process-tests/distributed-process-tests.cabal+158 distributed-process-tests/src/Control/Distributed/Process/Tests/CH.hs+1879 distributed-process-tests/src/Control/Distributed/Process/Tests/Closure.hs+589 distributed-process-tests/src/Control/Distributed/Process/Tests/Internal/Utils.hs+243 distributed-process-tests/src/Control/Distributed/Process/Tests/Mx.hs+542 distributed-process-tests/src/Control/Distributed/Process/Tests/Receive.hs+154 distributed-process-tests/src/Control/Distributed/Process/Tests/Stats.hs+129 distributed-process-tests/src/Control/Distributed/Process/Tests/Tracing.hs+431 distributed-process-tests/src/Network/Transport/Test.hs+12 distributed-process-tests/tests/runInMemory.hs+30 distributed-process-tests/tests/runTCP.hs+47 distributed-process.cabal+63−55 doc/semantics/CloudHaskellSemantics.tex+706 doc/semantics/Makefile+11 doc/semantics/references.bib+72 src/Control/Distributed/Process.hs+53−19 src/Control/Distributed/Process/Debug.hs+10−14 src/Control/Distributed/Process/Internal/BiMultiMap.hs+135 src/Control/Distributed/Process/Internal/CQueue.hs+66−15 src/Control/Distributed/Process/Internal/Closure/BuiltIn.hs+6−5 src/Control/Distributed/Process/Internal/Closure/Explicit.hs+1−2 src/Control/Distributed/Process/Internal/Closure/TH.hs+24−8 src/Control/Distributed/Process/Internal/Messaging.hs+36−26 src/Control/Distributed/Process/Internal/Primitives.hs+241−105 src/Control/Distributed/Process/Internal/Spawn.hs+13−9 src/Control/Distributed/Process/Internal/StrictMVar.hs+8−4 src/Control/Distributed/Process/Internal/Types.hs+150−28 src/Control/Distributed/Process/Internal/WeakTQueue.hs+7−2 src/Control/Distributed/Process/Management.hs+41−83 src/Control/Distributed/Process/Management/Internal/Agent.hs+2−1 src/Control/Distributed/Process/Management/Internal/Table.hs−222 src/Control/Distributed/Process/Management/Internal/Trace/Primitives.hs+10−3 src/Control/Distributed/Process/Management/Internal/Trace/Tracer.hs+12−5 src/Control/Distributed/Process/Management/Internal/Trace/Types.hs−10 src/Control/Distributed/Process/Management/Internal/Types.hs+9−19 src/Control/Distributed/Process/Node.hs+311−198 src/Control/Distributed/Process/Serializable.hs+6−14 src/Control/Distributed/Process/UnsafePrimitives.hs+109−29 stack-ghc-7.10.3.yaml+21 stack-ghc-8.0.2.yaml+23 stack-ghc-8.2.2.yaml+21 stack-ghc-8.4.4.yaml+20 stack-nightly.yaml+26 stack.yaml+22
.gitignore+3−14
@@ -1,15 +1,4 @@
1−tags
2−TAGS
31dist/
4−\#*#
5−*~
6−*.aux
7−*.hp
8−*.hi
9−*.o
10−*.prof
11−*.ps
12−*.swp
13−cabal-dev
14−cabal-dev*
15−.DS_Store
2+.cabal-sandbox
3+cabal.sandbox.config
4+.stack*
.travis.yml+36−15
@@ -1,22 +1,43 @@
1−language: haskell
1+language: c
22
3−ghc:
4− - 7.8
5− - 7.6
6− - 7.4
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
719
820before_install:
9− - cabal sandbox init
10− - for i in `cat REPOS`; do git clone http://github.com/haskell-distributed/$i; done
11− - for i in `cat REPOS`; do cabal sandbox add-source $i; done
12− - sudo apt-get update -qq
13− - sudo apt-get install -qq binutils-dev
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
1425
1526install:
16− # Don't run tests for dependencies.
17− - cabal install --only-dependencies
18− - cabal install --only-dependencies distributed-process-tests
27+- stack $ARGS setup --no-terminal;
28+ stack $ARGS build --no-terminal --only-snapshot --test --no-run-tests;
1929
2030script:
21− - cabal install
22− - cabal install --enable-tests -j1 distributed-process-tests
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.mdnew file+1
@@ -0,0 +1 @@
1+See https://github.com/haskell-distributed/cloud-haskell/blob/master/CONTRIBUTING.md.
ChangeLog+100
@@ -1,3 +1,103 @@
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+
11012014-12-09 Tim Watson <watson.timothy@gmail.com> 0.5.2
2102
3103* Fix docstring for `register`
LICENSE+1
@@ -1,4 +1,5 @@
11Copyright Well-Typed LLP, 2011-2012
2+Copyright Tweag I/O Limited, 2015
23
34All rights reserved.
45
Makefiledeleted−6
@@ -1,6 +0,0 @@
1−
2−ifneq (,$(BASE_DIR))
3−include $(BASE_DIR)/build.mk
4−else
5−include ../build.mk
6−endif
README.md+7−11
@@ -1,20 +1,16 @@
1−### distributed-process [![travis](https://secure.travis-ci.org/haskell-distributed/distributed-process.png)](http://travis-ci.org/haskell-distributed/distributed-process)
2−
1+# distributed-process
2+[![travis](https://secure.travis-ci.org/haskell-distributed/distributed-process.png)](http://travis-ci.org/haskell-distributed/distributed-process)
3+[![Release](https://img.shields.io/hackage/v/distributed-process.svg)](https://hackage.haskell.org/package/distributed-process)
34
45This repository is part of Cloud Haskell.
56
67See http://haskell-distributed.github.com for documentation, user guides,
78tutorials and assistance.
89
9−### Getting Help / Raising Issues
10−
11−Please visit our [bug tracker](http://cloud-haskell.atlassian.net) to submit
12−issues. Anyone can browse, although you'll need to provide an email address
13−and create an account in order to submit new issues.
10+## Getting Help / Raising Issues
1411
15−If you'd like to talk to a human, please contact us at the parallel-haskell
16−mailing list in the first instance - parallel-haskell@googlegroups.com.
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.
1713
18−### License
14+## License
1915
20−distributed-process is made available under a BSD-3 license.
16+This package is made available under a 3-clause BSD-style license.
REPOSdeleted−5
@@ -1,5 +0,0 @@
1−distributed-process-tests
2−distributed-static
3−network-transport
4−network-transport-tcp
5−rank1dynamic
benchmarks/Channels.hs+6−4
@@ -4,7 +4,7 @@ import Control.Monad
44import Control.Applicative
55import Control.Distributed.Process
66import Control.Distributed.Process.Node
7−import Network.Transport.TCP (createTransport, defaultTCPParameters)
7+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
88import Data.Binary (encode, decode)
99import qualified Data.ByteString.Lazy as BSL
1010
@@ -36,6 +36,8 @@ initialProcess "CLIENT" = do
3636main :: IO ()
3737main = do
3838 [role, host, port] <- getArgs
39− Right transport <- createTransport host port defaultTCPParameters
40− node <- newLocalNode transport initRemoteTable
41− runProcess node $ initialProcess role
39+ trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
40+ case trans of
41+ Right transport -> do node <- newLocalNode transport initRemoteTable
42+ runProcess node $ initialProcess role
43+ Left other -> error $ show other
benchmarks/Latency.hs+7−4
@@ -3,7 +3,7 @@ import Control.Monad
33import Control.Applicative
44import Control.Distributed.Process
55import Control.Distributed.Process.Node
6−import Network.Transport.TCP (createTransport, defaultTCPParameters)
6+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
77import Data.Binary (encode, decode)
88import qualified Data.ByteString.Lazy as BSL
99
@@ -31,6 +31,9 @@ initialProcess "CLIENT" = do
3131main :: IO ()
3232main = do
3333 [role, host, port] <- getArgs
34− Right transport <- createTransport host port defaultTCPParameters
35− node <- newLocalNode transport initRemoteTable
36− runProcess node $ initialProcess role
34+ trans <- createTransport
35+ (defaultTCPAddr host port) defaultTCPParameters
36+ case trans of
37+ Right transport -> do node <- newLocalNode transport initRemoteTable
38+ runProcess node $ initialProcess role
39+ Left other -> error $ show other
benchmarks/ProcessRing.hs+7−3
@@ -4,11 +4,15 @@ To run the benchmarks, select a value for the ring size (sz) and
44the number of times to send a message around the ring
55
66-}
7+
8+{-# LANGUAGE BangPatterns #-}
9+{-# LANGUAGE ScopedTypeVariables #-}
10+
711import Control.Monad
812import Control.Distributed.Process hiding (catch)
913import Control.Distributed.Process.Node
1014import Control.Exception (catch, SomeException)
11−import Network.Transport.TCP (createTransport, defaultTCPParameters)
15+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
1216import System.Environment
1317import System.Console.GetOpt
1418
@@ -106,8 +110,8 @@ main = do
106110 argv <- getArgs
107111 (opt, _) <- parseArgv argv
108112 putStrLn $ "options: " ++ (show opt)
109− Right transport <- createTransport "127.0.0.1" "8090" defaultTCPParameters
113+ Right transport <- createTransport
114+ (defaultTCPAddr "127.0.0.1" "8090" ) defaultTCPParameters
110115 node <- newLocalNode transport initRemoteTable
111116 catch (void $ runProcess node $ initialProcess opt)
112117 (\(e :: SomeException) -> putStrLn $ "ERROR: " ++ (show e))
113−
benchmarks/Spawns.hs+8−4
@@ -1,3 +1,5 @@
1+{-# LANGUAGE BangPatterns #-}
2+
13-- | Like Throughput, but send every ping from a different process
24-- (i.e., require a lightweight connection per ping)
35import System.Environment
@@ -5,7 +7,7 @@ import Control.Monad
57import Control.Applicative
68import Control.Distributed.Process
79import Control.Distributed.Process.Node
8−import Network.Transport.TCP (createTransport, defaultTCPParameters)
10+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
911import Data.Binary (encode, decode)
1012import qualified Data.ByteString.Lazy as BSL
1113
@@ -40,6 +42,8 @@ initialProcess "CLIENT" = do
4042main :: IO ()
4143main = do
4244 [role, host, port] <- getArgs
43− Right transport <- createTransport host port defaultTCPParameters
44− node <- newLocalNode transport initRemoteTable
45− runProcess node $ initialProcess role
45+ trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
46+ case trans of
47+ Right transport -> do node <- newLocalNode transport initRemoteTable
48+ runProcess node $ initialProcess role
49+ Left other -> error $ show other
benchmarks/Throughput.hs+8−4
@@ -1,10 +1,12 @@
1+{-# LANGUAGE BangPatterns #-}
12{-# LANGUAGE DeriveDataTypeable #-}
3+
24import System.Environment
35import Control.Monad
46import Control.Applicative
57import Control.Distributed.Process
68import Control.Distributed.Process.Node
7−import Network.Transport.TCP (createTransport, defaultTCPParameters)
9+import Network.Transport.TCP (createTransport, defaultTCPParameters, defaultTCPAddr)
810import Data.Binary
911import qualified Data.ByteString.Lazy as BSL
1012import Data.Typeable
@@ -65,6 +67,8 @@ initialProcess "CLIENT" = do
6567main :: IO ()
6668main = do
6769 [role, host, port] <- getArgs
68− Right transport <- createTransport host port defaultTCPParameters
69− node <- newLocalNode transport initRemoteTable
70− runProcess node $ initialProcess role
70+ trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
71+ case trans of
72+ Right transport -> do node <- newLocalNode transport initRemoteTable
73+ runProcess node $ initialProcess role
74+ Left other -> error $ show other
distributed-process-tests/.gitignorenew file+4
@@ -0,0 +1,4 @@
1+dist/
2+.cabal-sandbox
3+cabal.sandbox.config
4+.stack*
distributed-process-tests/CONTRIBUTING.mdnew file+1
@@ -0,0 +1 @@
1+See https://github.com/haskell-distributed/cloud-haskell/blob/master/CONTRIBUTING.md.
distributed-process-tests/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-tests/README.mdnew file+14
@@ -0,0 +1,14 @@
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/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
distributed-process-tests/distributed-process-tests.cabalnew file+158
@@ -0,0 +1,158 @@
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.hsnew file+1879
@@ -0,0 +1,1879 @@
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.hsnew file+589
@@ -0,0 +1,589 @@
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.hsnew file+243
@@ -0,0 +1,243 @@
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.hsnew file+542
@@ -0,0 +1,542 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE ParallelListComp #-}
3+
4+module Control.Distributed.Process.Tests.Mx (tests) where
5+
6+import Control.Distributed.Process.Tests.Internal.Utils
7+import Network.Transport.Test (TestTransport(..))
8+import Control.Distributed.Process hiding (bracket, finally, try)
9+import Control.Distributed.Process.Internal.Types
10+ ( ProcessExitException(..)
11+ , unsafeCreateUnencodedMessage
12+ )
13+import Control.Distributed.Process.Node
14+import qualified Control.Distributed.Process.UnsafePrimitives as Unsafe
15+ ( send
16+ , nsend
17+ , nsendRemote
18+ , usend
19+ , sendChan
20+ )
21+import Control.Distributed.Process.Management
22+ ( MxEvent(..)
23+ , MxAgentId(..)
24+ , mxAgent
25+ , mxSink
26+ , mxReady
27+ , mxReceive
28+ , mxDeactivate
29+ , liftMX
30+ , mxGetLocal
31+ , mxSetLocal
32+ , mxUpdateLocal
33+ , mxNotify
34+ , mxBroadcast
35+ )
36+import Control.Monad (void, unless)
37+import Control.Monad.Catch(finally, bracket, try)
38+import Control.Rematch (equalTo)
39+import Data.Binary
40+import Data.List (find, sort, intercalate)
41+import Data.Maybe (isJust, fromJust, isNothing, fromMaybe, catMaybes)
42+import Data.Typeable
43+import GHC.Generics hiding (from)
44+#if ! MIN_VERSION_base(4,6,0)
45+import Prelude hiding (catch, log)
46+#endif
47+
48+import Test.Framework
49+ ( Test
50+ , testGroup
51+ )
52+import Test.Framework.Providers.HUnit (testCase)
53+
54+data Publish = Publish
55+ deriving (Typeable, Generic, Eq)
56+
57+instance Binary Publish where
58+
59+awaitExit :: ProcessId -> Process ()
60+awaitExit pid =
61+ withMonitorRef pid $ \ref -> do
62+ receiveWait
63+ [ matchIf (\(ProcessMonitorNotification r _ _) -> r == ref)
64+ (\_ -> return ())
65+ ]
66+ where
67+ withMonitorRef p = bracket (monitor p) unmonitor
68+
69+testAgentBroadcast :: TestResult (Maybe ()) -> Process ()
70+testAgentBroadcast result = do
71+ (resultSP, resultRP) <- newChan :: Process (SendPort (), ReceivePort ())
72+
73+ publisher <- mxAgent (MxAgentId "publisher-agent") () [
74+ mxSink $ \() -> mxBroadcast Publish >> mxReady
75+ ]
76+
77+ consumer <- mxAgent (MxAgentId "consumer-agent") () [
78+ mxSink $ \Publish -> (liftMX $ sendChan resultSP ()) >> mxReady
79+ ]
80+
81+ mxNotify ()
82+ -- Once the publisher has seen our message, it will broadcast the Publish
83+ -- and the consumer will see that and send the result to our typed channel.
84+ stash result =<< receiveChanTimeout 10000000 resultRP
85+
86+ kill publisher "finished"
87+ kill consumer "finished"
88+
89+testAgentDualInput :: TestResult (Maybe Int) -> Process ()
90+testAgentDualInput result = do
91+ (sp, rp) <- newChan
92+ s <- mxAgent (MxAgentId "sum-agent") (0 :: Int) [
93+ mxSink $ (\(i :: Int) -> do
94+ mxSetLocal . (+i) =<< mxGetLocal
95+ i' <- mxGetLocal
96+ if i' == 15
97+ then do mxGetLocal >>= liftMX . sendChan sp
98+ mxDeactivate "finished"
99+ else mxReady)
100+ ]
101+
102+ mRef <- monitor s
103+
104+ mxNotify (1 :: Int)
105+ nsend "sum-agent" (3 :: Int)
106+ mxNotify (2 :: Int)
107+ nsend "sum-agent" (4 :: Int)
108+ mxNotify (5 :: Int)
109+
110+ stash result =<< receiveChanTimeout 10000000 rp
111+ died <- receiveTimeout 10000000 [
112+ matchIf (\(ProcessMonitorNotification r _ _) -> r == mRef) (const $ return True)
113+ ]
114+ died `shouldBe` equalTo (Just True)
115+
116+testAgentPrioritisation :: TestResult [String] -> Process ()
117+testAgentPrioritisation result = do
118+
119+ -- TODO: this isn't really testing how we /prioritise/ one source
120+ -- over another at all, but I've not yet figured out the right way
121+ -- to do so, since we're at the whim of the scheduler with regards
122+ -- the timeliness of nsend versus mxNotify anyway.
123+
124+ let name = "prioritising-agent"
125+ (sp, rp) <- newChan
126+ s <- mxAgent (MxAgentId name) ["first"] [
127+ mxSink (\(s :: String) -> do
128+ mxUpdateLocal (s:)
129+ st <- mxGetLocal
130+ case length st of
131+ n | n == 5 -> do liftMX $ sendChan sp (sort st)
132+ mxDeactivate "finished"
133+ _ -> mxReceive -- go to the mailbox
134+ )
135+ ]
136+
137+ nsend name "second"
138+ mxNotify "third"
139+ mxNotify "fourth"
140+ nsend name "fifth"
141+
142+ stash result . fromMaybe [] =<< receiveChanTimeout 10000000 rp
143+ awaitExit s
144+
145+testAgentMailboxHandling :: TestResult (Maybe ()) -> Process ()
146+testAgentMailboxHandling result = do
147+ (sp, rp) <- newChan
148+ agent <- mxAgent (MxAgentId "mailbox-agent") () [
149+ mxSink $ \() -> (liftMX $ sendChan sp ()) >> mxReady
150+ ]
151+
152+ nsend "mailbox-agent" ()
153+
154+ stash result =<< receiveChanTimeout 1000000 rp
155+ kill agent "finished"
156+
157+testAgentEventHandling :: TestResult Bool -> Process ()
158+testAgentEventHandling result = do
159+ us <- getSelfPid
160+ -- because this test is a bit racy, let's ensure it can't run indefinitely
161+ timer <- spawnLocal $ do
162+ pause (1000000 * 60 * 5)
163+ -- okay we've waited 5 mins, let's kill the test off if it's stuck...
164+ stash result False
165+ kill us "Test Timed Out"
166+
167+ let initState = [] :: [MxEvent]
168+ (rc, rs) <- newChan
169+ agentPid <- mxAgent (MxAgentId "lifecycle-listener-agent") initState [
170+ (mxSink $ \ev -> do
171+ st <- mxGetLocal
172+ let act =
173+ case ev of
174+ (MxSpawned _) -> mxSetLocal (ev:st)
175+ (MxProcessDied _ _) -> mxSetLocal (ev:st)
176+ _ -> return ()
177+ act >> (liftMX $ sendChan rc ()) >> mxReady),
178+ (mxSink $ \(ev, sp :: SendPort Bool) -> do
179+ st <- mxGetLocal
180+ let found =
181+ case ev of
182+ MxSpawned p ->
183+ isJust $ find (\ev' ->
184+ case ev' of
185+ (MxSpawned p') -> p' == p
186+ _ -> False) st
187+ MxProcessDied p r ->
188+ isJust $ find (\ev' ->
189+ case ev' of
190+ (MxProcessDied p' r') -> p' == p && r == r'
191+ _ -> False) st
192+ _ -> False
193+ liftMX $ sendChan sp found
194+ mxReady)
195+ ]
196+
197+ -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
198+ faff 2000000 rs
199+
200+ (sp, rp) <- newChan :: Process (SendPort (), ReceivePort ())
201+
202+ pid <- spawnLocal $ expect >>= sendChan sp
203+
204+ -- By waiting for a monitor notification, we have a
205+ -- higher probably that the agent has seen the spawn and died events
206+ monitor pid
207+
208+ send pid ()
209+ rct <- receiveChanTimeout 10000000 rp
210+ unless (isJust rct) $ die "No response on channel"
211+
212+ pmn <- receiveTimeout 2000000 [ match (\ProcessMonitorNotification{} -> return ()) ]
213+ unless (isJust pmn) $ die "No monitor notification arrived"
214+
215+ -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
216+ faff 2000000 rs
217+
218+ (replyTo, reply) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
219+ mxNotify (MxSpawned pid, replyTo)
220+ mxNotify (MxProcessDied pid DiedNormal, replyTo)
221+
222+ seenAlive <- receiveChanTimeout 10000000 reply
223+ seenDead <- receiveChanTimeout 10000000 reply
224+
225+ stash result $ foldr (&&) True $ catMaybes [seenAlive, seenDead]
226+ kill timer "test-complete"
227+ kill agentPid "test-complete"
228+ where
229+ faff delay port = do
230+ res <- receiveChanTimeout delay port
231+ unless (isNothing res) $ pause delay
232+
233+testMxRegEvents :: TestResult () -> Process ()
234+testMxRegEvents result = do
235+ {- This test only deals with the local case, to ensure that we are being
236+ notified in the expected order - the remote cases related to the
237+ behaviour of the node controller are contained in the CH test suite. -}
238+ ensure (stash result ()) $ do
239+ let label = "testMxRegEvents"
240+ let agentLabel = "mxRegEvents-agent"
241+ let delay = 1000000
242+ (regChan, regSink) <- newChan
243+ (unRegChan, unRegSink) <- newChan
244+ agent <- mxAgent (MxAgentId agentLabel) () [
245+ mxSink $ \ev -> do
246+ case ev of
247+ MxRegistered pid label'
248+ | label' == label -> liftMX $ sendChan regChan (label', pid)
249+ MxUnRegistered pid label'
250+ | label' == label -> liftMX $ sendChan unRegChan (label', pid)
251+ _ -> return ()
252+ mxReady
253+ ]
254+
255+ p1 <- spawnLocal expect
256+ p2 <- spawnLocal expect
257+
258+ register label p1
259+ reg1 <- receiveChanTimeout delay regSink
260+ reg1 `shouldBe` equalTo (Just (label, p1))
261+
262+ unregister label
263+ unreg1 <- receiveChanTimeout delay unRegSink
264+ unreg1 `shouldBe` equalTo (Just (label, p1))
265+
266+ register label p2
267+ reg2 <- receiveChanTimeout delay regSink
268+ reg2 `shouldBe` equalTo (Just (label, p2))
269+
270+ reregister label p1
271+ unreg2 <- receiveChanTimeout delay unRegSink
272+ unreg2 `shouldBe` equalTo (Just (label, p2))
273+
274+ reg3 <- receiveChanTimeout delay regSink
275+ reg3 `shouldBe` equalTo (Just (label, p1))
276+
277+ mapM_ (flip kill $ "test-complete") [agent, p1, p2]
278+
279+testMxRegMon :: LocalNode -> TestResult () -> Process ()
280+testMxRegMon remoteNode result = do
281+ ensure (stash result ()) $ do
282+ -- ensure that when a registered process dies, we get a notification that
283+ -- it has been unregistered as well as seeing the name get removed
284+ let label1 = "aaaaa"
285+ let label2 = "bbbbb"
286+ let isValid l = l == label1 || l == label2
287+ let agentLabel = "mxRegMon-agent"
288+ let delay = 1000000
289+ (regChan, regSink) <- newChan
290+ (unRegChan, unRegSink) <- newChan
291+ agent <- mxAgent (MxAgentId agentLabel) () [
292+ mxSink $ \ev -> do
293+ case ev of
294+ MxRegistered pid label
295+ | isValid label -> liftMX $ sendChan regChan (label, pid)
296+ MxUnRegistered pid label
297+ | isValid label -> liftMX $ sendChan unRegChan (label, pid)
298+ _ -> return ()
299+ mxReady
300+ ]
301+
302+ (sp, rp) <- newChan
303+ liftIO $ forkProcess remoteNode $ do
304+ getSelfPid >>= sendChan sp
305+ expect :: Process ()
306+
307+ p1 <- receiveChan rp
308+
309+ register label1 p1
310+ reg1 <- receiveChanTimeout delay regSink
311+ reg1 `shouldBe` equalTo (Just (label1, p1))
312+
313+ register label2 p1
314+ reg2 <- receiveChanTimeout delay regSink
315+ reg2 `shouldBe` equalTo (Just (label2, p1))
316+
317+ n1 <- whereis label1
318+ n1 `shouldBe` equalTo (Just p1)
319+
320+ n2 <- whereis label2
321+ n2 `shouldBe` equalTo (Just p1)
322+
323+ kill p1 "goodbye"
324+
325+ unreg1 <- receiveChanTimeout delay unRegSink
326+ unreg2 <- receiveChanTimeout delay unRegSink
327+
328+ let evts = [unreg1, unreg2]
329+ -- we can't rely on the order of the values in the node controller's
330+ -- map (it's either racy to do so, or no such guarantee exists for Data.Map),
331+ -- so we simply verify that we received the un-registration events we expect
332+ evts `shouldContain` (Just (label1, p1))
333+ evts `shouldContain` (Just (label2, p1))
334+
335+ kill agent "test-complete"
336+
337+ensure :: Process () -> Process () -> Process ()
338+ensure = flip finally
339+
340+testNSend :: (String -> () -> Process ())
341+ -> Maybe LocalNode
342+ -> Process ()
343+testNSend op n = do
344+ us <- getSelfPid
345+ let delay = 5000000
346+ let label = "testMxSend" ++ (show us)
347+ let isValid = isValidLabel n label
348+
349+ testMxSend n label $ \p1 sink -> do
350+ register label p1
351+ reg1 <- receiveChanTimeout delay sink
352+ case reg1 of
353+ Just (MxRegistered pd lb)
354+ | pd == p1 && isValid lb -> return ()
355+ _ -> die $ "Reg-Failed: " ++ show reg1
356+
357+ op label ()
358+
359+ sent <- receiveChanTimeout delay sink
360+ case sent of
361+ Just (MxSentToName lb by _)
362+ | by == us && isValid lb -> return True
363+ _ -> die $ "Send-Failed: " ++ show sent
364+
365+ where
366+ isValidLabel nd l1 l2
367+ | l2 == l2 = True
368+ | isJust nd = l2 == l1 ++ "@" ++ show (localNodeId $ fromJust nd)
369+ | otherwise = False
370+
371+testSend :: (ProcessId -> () -> Process ())
372+ -> Maybe LocalNode
373+ -> Process ()
374+testSend op n = do
375+ us <- getSelfPid
376+ let label = "testMxSend" ++ (show us)
377+ testMxSend n label $ \p1 sink -> do
378+ -- initiate a send
379+ op p1 ()
380+
381+ -- verify the management event
382+ sent <- receiveChanTimeout 5000000 sink
383+ case sent of
384+ Just (MxSent pidTo pidFrom _)
385+ | pidTo == p1 && pidFrom == us -> return True
386+ _ -> return False
387+
388+testChan :: (SendPort () -> () -> Process ())
389+ -> Maybe LocalNode
390+ -> Process ()
391+testChan op n = testMxSend n "" $ \p1 sink -> do
392+
393+ us <- getSelfPid
394+ send p1 us
395+
396+ cleared <- receiveChanTimeout 2000000 sink
397+ case cleared of
398+ Just (MxSent pidTo pidFrom _)
399+ | pidTo == p1 && pidFrom == us -> return ()
400+ _ -> die "Received uncleared Mx Event"
401+
402+ chan <- receiveTimeout 5000000 [ match return ]
403+ let ch' = fromMaybe (error "No reply chan received") chan
404+
405+ -- initiate a send
406+ op ch' ()
407+
408+ -- verify the management event
409+ sent <- receiveChanTimeout 5000000 sink
410+ case sent of
411+ Just (MxSentToPort sId spId _)
412+ | sId == us && spId == sendPortId ch' -> return True
413+ _ -> return False
414+
415+type SendTest = ProcessId -> ReceivePort MxEvent -> Process Bool
416+
417+testMxSend :: Maybe LocalNode -> String -> SendTest -> Process ()
418+testMxSend mNode label test = do
419+ us <- getSelfPid
420+ (sp, rp) <- newChan
421+ (chan, sink) <- newChan
422+ agent <- mxAgent (MxAgentId $ agentLabel us) () [
423+ mxSink $ \ev -> do
424+ case ev of
425+ m@(MxSentToPort _ cid _)
426+ | cid /= sendPortId chan
427+ && cid /= sendPortId sp -> liftMX $ sendChan chan m
428+ m@(MxSent _ fromPid _)
429+ | fromPid == us -> liftMX $ sendChan chan m
430+ m@(MxSentToName _ fromPid _)
431+ | fromPid == us -> liftMX $ sendChan chan m
432+ m@(MxRegistered _ name)
433+ | name == label -> liftMX $ sendChan chan m
434+ _ -> return ()
435+ mxReady
436+ ]
437+
438+ case mNode of
439+ Nothing -> void $ spawnLocal (proc sp)
440+ Just remoteNode -> void $ liftIO $ forkProcess remoteNode $ proc sp
441+
442+ p1 <- receiveChanTimeout 2000000 rp
443+ unless (isJust p1) $ die "Timed out waiting for ProcessId"
444+ res <- try $ test (fromJust p1) sink
445+
446+ kill agent "bye"
447+ kill (fromJust p1) "bye"
448+
449+ case res of
450+ Left (ProcessExitException _ m) -> (liftIO $ putStrLn $ "SomeException-" ++ show m) >> die m
451+ Right tr -> tr `shouldBe` equalTo True
452+
453+
454+ where
455+ agentLabel s = "mx-unsafe-check-agent-" ++ show s
456+ proc sp' = getSelfPid >>= sendChan sp' >> go Nothing
457+
458+ go :: Maybe (ReceivePort ()) -> Process ()
459+ go Nothing = receiveTimeout 5000000 [ match replyChannel ] >>= go
460+ go c@(Just c') = receiveWait [ matchChan c' return ] >> go c
461+
462+ replyChannel p' = do
463+ (s, r) <- newChan
464+ send p' s
465+ return r
466+
467+tests :: TestTransport -> IO [Test]
468+tests TestTransport{..} = do
469+ node1 <- newLocalNode testTransport initRemoteTable
470+ node2 <- newLocalNode testTransport initRemoteTable
471+ return [
472+ testGroup "MxAgents" [
473+ testCase "EventHandling"
474+ (delayedAssertion
475+ "expected True, but events where not as expected"
476+ node1 True testAgentEventHandling)
477+ , testCase "InterAgentBroadcast"
478+ (delayedAssertion
479+ "expected (), but no broadcast was received"
480+ node1 (Just ()) testAgentBroadcast)
481+ , testCase "AgentMailboxHandling"
482+ (delayedAssertion
483+ "expected (Just ()), but no regular (mailbox) input was handled"
484+ node1 (Just ()) testAgentMailboxHandling)
485+ , testCase "AgentDualInputHandling"
486+ (delayedAssertion
487+ "expected sum = 15, but the result was Nothing"
488+ node1 (Just 15 :: Maybe Int) testAgentDualInput)
489+ , testCase "AgentInputPrioritisation"
490+ (delayedAssertion
491+ "expected [first, second, third, fourth, fifth], but result diverged"
492+ node1 (sort ["first", "second",
493+ "third", "fourth",
494+ "fifth"]) testAgentPrioritisation)
495+ ]
496+ , testGroup "MxEvents" [
497+ testCase "NameRegistrationEvents"
498+ (delayedAssertion
499+ "expected registration events to map to the correct ProcessId"
500+ node1 () testMxRegEvents)
501+ , testCase "PostDeathNameUnRegistrationEvents"
502+ (delayedAssertion
503+ "expected process deaths to result in unregistration events"
504+ node1 () (testMxRegMon node2))
505+ , testGroup "SendEvents" $ buildTestCases node1 node2
506+ ]
507+ ]
508+ where
509+ buildTestCases n1 n2 = let nid = localNodeId n2 in build n1 n2 [
510+ ("NSend", [
511+ ("nsend", testNSend nsend)
512+ , ("Unsafe.nsend", testNSend Unsafe.nsend)
513+ , ("nsendRemote", testNSend (nsendRemote nid))
514+ , ("Unsafe.nsendRemote", testNSend (Unsafe.nsendRemote nid))
515+ ])
516+ , ("Send", [
517+ ("send", testSend send)
518+ , ("Unsafe.send", testSend Unsafe.send)
519+ , ("usend", testSend usend)
520+ , ("Unsafe.usend", testSend Unsafe.usend)
521+ , ("sendChan", testChan sendChan)
522+ , ("Unsafe.sendChan", testChan Unsafe.sendChan)
523+ ])
524+ , ("Forward", [
525+ ("forward", testSend (\p m -> forward (unsafeCreateUnencodedMessage m) p))
526+ , ("uforward", testSend (\p m -> uforward (unsafeCreateUnencodedMessage m) p))
527+ ])
528+ ]
529+
530+ build :: LocalNode
531+ -> LocalNode
532+ -> [(String, [(String, (Maybe LocalNode -> Process ()))])]
533+ -> [Test]
534+ build n ln specs =
535+ [ testGroup (intercalate "-" [groupName, caseSuffix]) [
536+ testCase (intercalate "-" [caseName, caseSuffix])
537+ (runProcess n (caseImpl caseNode))
538+ | (caseName, caseImpl) <- groupCases
539+ ]
540+ | (groupName, groupCases) <- specs
541+ , (caseSuffix, caseNode) <- [("RemotePid", Just ln), ("LocalPid", Nothing)]
542+ ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Receive.hsnew file+154
@@ -0,0 +1,154 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+{-# OPTIONS_GHC -Wall #-}
3+
4+-- | XXX test doesn't work, because failure exceptions don't get propagated. The
5+-- test always claims to succeed, even if it failed.
6+
7+module Control.Distributed.Process.Tests.Receive (tests) where
8+
9+import Network.Transport.Test (TestTransport(..))
10+
11+import Network.Transport (Transport)
12+import Control.Distributed.Process
13+import Control.Distributed.Process.Node
14+
15+import Control.Monad
16+
17+import Test.HUnit (Assertion, (@?=))
18+import Test.Framework (Test)
19+import Test.Framework.Providers.HUnit (testCase)
20+
21+-- Tests:
22+
23+-- 1. 2 matchChans, receive on each one
24+-- 2. matchChan/matchIf, receive on each one
25+-- 3. matchIf/matchChan, receive on each one
26+-- 4. matchIf/matchChan/matchIf, receive on each one
27+
28+recTest1 :: ReceivePort ()
29+ -> SendPort String
30+ -> ReceivePort String -> ReceivePort String
31+ -> Process ()
32+recTest1 wait sync r1 r2 = do
33+ forever $ do
34+ receiveChan wait
35+ r <- receiveWait
36+ [ matchChan r1 $ \s -> return ("received1 " ++ s)
37+ , matchChan r2 $ \s -> return ("received2 " ++ s)
38+ ]
39+ sendChan sync r
40+
41+recTest2 :: ReceivePort ()
42+ -> SendPort String
43+ -> ReceivePort String -> ReceivePort String
44+ -> Process ()
45+recTest2 wait sync r1 _ = do
46+ forever $ do
47+ receiveChan wait
48+ r <- receiveWait
49+ [ matchChan r1 $ \s -> return ("received1 " ++ s)
50+ , matchIf (== "foo") $ \s -> return ("received2 " ++ s)
51+ ]
52+ sendChan sync r
53+
54+recTest3 :: ReceivePort ()
55+ -> SendPort String
56+ -> ReceivePort String -> ReceivePort String
57+ -> Process ()
58+recTest3 wait sync r1 _ = do
59+ forever $ do
60+ receiveChan wait
61+ r <- receiveWait
62+ [ matchIf (== "foo") $ \s -> return ("received1 " ++ s)
63+ , matchChan r1 $ \s -> return ("received2 " ++ s)
64+ ]
65+ sendChan sync r
66+
67+recTest4 :: ReceivePort ()
68+ -> SendPort String
69+ -> ReceivePort String -> ReceivePort String
70+ -> Process ()
71+recTest4 wait sync r1 _ = do
72+ forever $ do
73+ receiveChan wait
74+ r <- receiveWait
75+ [ matchIf (== "foo") $ \s -> return ("received1 " ++ s)
76+ , matchChan r1 $ \s -> return ("received2 " ++ s)
77+ , matchIf (== "bar") $ \s -> return ("received3 " ++ s)
78+ ]
79+ sendChan sync r
80+
81+master :: Process ()
82+master = do
83+ (waits,waitr) <- newChan
84+ (syncs,syncr) <- newChan
85+ let go expected = do
86+ sendChan waits ()
87+ r <- receiveChan syncr
88+ liftIO $ print (r, expected, r == expected)
89+ liftIO $ r @?= expected
90+
91+ liftIO $ putStrLn "---- Test 1 ----"
92+ (s1,r1) <- newChan
93+ (s2,r2) <- newChan
94+ p <- spawnLocal (recTest1 waitr syncs r1 r2)
95+
96+ sendChan s1 "a" >> go "received1 a"
97+ sendChan s2 "b" >> go "received2 b"
98+ sendChan s1 "a" >> sendChan s2 "b" >> go "received1 a"
99+ go "received2 b"
100+
101+ kill p "BANG"
102+
103+ liftIO $ putStrLn "\n---- Test 2 ----"
104+ (s1,r1) <- newChan
105+ (s2,r2) <- newChan
106+ p <- spawnLocal (recTest2 waitr syncs r1 r2)
107+
108+ sendChan s1 "a" >> go "received1 a"
109+ send p "foo" >> go "received2 foo"
110+ sendChan s1 "a" >> send p "foo" >> go "received1 a"
111+ sendChan s1 "a" >> send p "bar" >> go "received1 a"
112+ go "received2 foo"
113+
114+ kill p "BANG"
115+
116+ liftIO $ putStrLn "\n---- Test 3 ----"
117+ (s1,r1) <- newChan
118+ (s2,r2) <- newChan
119+ p <- spawnLocal (recTest3 waitr syncs r1 r2)
120+
121+ sendChan s1 "a" >> go "received2 a"
122+ send p "foo" >> go "received1 foo"
123+ sendChan s1 "a" >> send p "foo" >> go "received1 foo"
124+ sendChan s1 "a" >> send p "bar" >> go "received2 a"
125+ go "received2 a"
126+
127+ kill p "BANG"
128+
129+ liftIO $ putStrLn "\n---- Test 4 ----"
130+ (s1,r1) <- newChan
131+ (s2,r2) <- newChan
132+ p <- spawnLocal (recTest4 waitr syncs r1 r2)
133+
134+ sendChan s1 "a" >> go "received2 a"
135+ send p "foo" >> go "received1 foo"
136+ send p "bar" >> go "received3 bar"
137+ sendChan s1 "a" >> send p "foo" >> go "received1 foo"
138+ send p "bar" >> go "received2 a"
139+ send p "foo" >> go "received1 foo" >> go "received3 bar"
140+
141+ kill p "BANG"
142+
143+ terminate
144+
145+testReceive :: Transport -> RemoteTable -> Assertion
146+testReceive transport rtable = do
147+ node <- newLocalNode transport rtable
148+ runProcess node $ master
149+
150+tests :: TestTransport -> IO [Test]
151+tests TestTransport{..} = do
152+ let rtable = initRemoteTable
153+ return
154+ [ testCase "testReceive" (testReceive testTransport rtable) ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Stats.hsnew file+129
@@ -0,0 +1,129 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# OPTIONS_GHC -fno-warn-orphans #-}
3+module Control.Distributed.Process.Tests.Stats (tests) where
4+
5+import Control.Distributed.Process.Tests.Internal.Utils
6+import Network.Transport.Test (TestTransport(..))
7+
8+import Control.Concurrent.MVar
9+ ( MVar
10+ , newEmptyMVar
11+ , putMVar
12+ , takeMVar
13+ )
14+import Control.Distributed.Process
15+import Control.Distributed.Process.Node
16+import Data.Binary ()
17+import Data.Typeable ()
18+
19+#if ! MIN_VERSION_base(4,6,0)
20+import Prelude hiding (catch)
21+#endif
22+
23+import Test.Framework
24+ ( Test
25+ , testGroup
26+ )
27+import Test.HUnit (Assertion)
28+import Test.Framework.Providers.HUnit (testCase)
29+
30+testLocalDeadProcessInfo :: TestResult (Maybe ProcessInfo) -> Process ()
31+testLocalDeadProcessInfo result = do
32+ pid <- spawnLocal $ do (_ :: String) <- expect; return ()
33+ mref <- monitor pid
34+ send pid "finish"
35+ _ <- receiveWait [
36+ matchIf (\(ProcessMonitorNotification ref' pid' r) ->
37+ ref' == mref && pid' == pid && r == DiedNormal)
38+ (\p -> return p)
39+ ]
40+ getProcessInfo pid >>= stash result
41+
42+testLocalLiveProcessInfo :: TestResult Bool -> Process ()
43+testLocalLiveProcessInfo result = do
44+ self <- getSelfPid
45+ node <- getSelfNode
46+ register "foobar" self
47+
48+ mon <- liftIO $ newEmptyMVar
49+ -- TODO: we can't get the mailbox's length
50+ -- mapM (send self) ["hello", "there", "mr", "process"]
51+ pid <- spawnLocal $ do
52+ link self
53+ mRef <- monitor self
54+ stash mon mRef
55+ res <- expect
56+ case res of
57+ "die" -> return ()
58+ _ -> die $ "unexpected message received: " ++ res
59+
60+ monRef <- liftIO $ takeMVar mon
61+
62+ mpInfo <- getProcessInfo self
63+ case mpInfo of
64+ Nothing -> stash result False
65+ Just p -> verifyPInfo p pid monRef node
66+ where verifyPInfo :: ProcessInfo
67+ -> ProcessId
68+ -> MonitorRef
69+ -> NodeId
70+ -> Process ()
71+ verifyPInfo pInfo pid mref node =
72+ stash result $ infoNode pInfo == node &&
73+ infoLinks pInfo == [pid] &&
74+ infoMonitors pInfo == [(pid, mref)] &&
75+-- infoMessageQueueLength pInfo == Just 4 &&
76+ infoRegisteredNames pInfo == ["foobar"]
77+
78+testRemoteLiveProcessInfo :: TestTransport -> LocalNode -> Assertion
79+testRemoteLiveProcessInfo TestTransport{..} node1 = do
80+ serverAddr <- liftIO $ newEmptyMVar :: IO (MVar ProcessId)
81+ liftIO $ launchRemote serverAddr
82+ serverPid <- liftIO $ takeMVar serverAddr
83+ withActiveRemote node1 $ \result -> do
84+ self <- getSelfPid
85+ link serverPid
86+ -- our send op shouldn't overtake link or monitor requests AFAICT
87+ -- so a little table tennis should get us synchronised properly
88+ send serverPid (self, "ping")
89+ pong <- expect
90+ pInfo <- getProcessInfo serverPid
91+ stash result $ pong == "pong" && pInfo /= Nothing
92+ where
93+ launchRemote :: MVar ProcessId -> IO ()
94+ launchRemote locMV = do
95+ node2 <- liftIO $ newLocalNode testTransport initRemoteTable
96+ _ <- liftIO $ forkProcess node2 $ do
97+ self <- getSelfPid
98+ liftIO $ putMVar locMV self
99+ _ <- receiveWait [
100+ match (\(pid, "ping") -> send pid "pong")
101+ ]
102+ res <- expect
103+ case res of
104+ "stop" -> return ()
105+ _ -> die $ "unexpected message received: " ++ res
106+ return ()
107+
108+ withActiveRemote :: LocalNode
109+ -> ((TestResult Bool -> Process ()) -> Assertion)
110+ withActiveRemote n = do
111+ a <- delayedAssertion "getProcessInfo remotePid failed" n True
112+ return a
113+
114+tests :: TestTransport -> IO [Test]
115+tests testtrans@TestTransport{..} = do
116+ node1 <- newLocalNode testTransport initRemoteTable
117+ return [
118+ testGroup "Process Info" [
119+ testCase "testLocalDeadProcessInfo"
120+ (delayedAssertion
121+ "expected dead process-info to be ProcessInfoNone"
122+ node1 (Nothing) testLocalDeadProcessInfo)
123+ , testCase "testLocalLiveProcessInfo"
124+ (delayedAssertion
125+ "expected process-info to be correctly populated"
126+ node1 True testLocalLiveProcessInfo)
127+ , testCase "testRemoveLiveProcessInfo"
128+ (testRemoteLiveProcessInfo testtrans node1)
129+ ] ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Tracing.hsnew file+431
@@ -0,0 +1,431 @@
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.hsnew file+12
@@ -0,0 +1,12 @@
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.hsnew file+30
@@ -0,0 +1,30 @@
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.hsnew file+47
@@ -0,0 +1,47 @@
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.cabal+63−55
@@ -1,12 +1,12 @@
11Name: distributed-process
2−Version: 0.5.3
2+Version: 0.7.4
33Cabal-Version: >=1.8
44Build-Type: Simple
55License: BSD3
66License-File: LICENSE
7−Copyright: Well-Typed LLP
7+Copyright: Well-Typed LLP, Tweag I/O Limited
88Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
9−Maintainer: watson.timothy@gmail.com, edsko@well-typed.com, duncan@well-typed.com
9+Maintainer: Tim Watson <watson.timothy@gmail.com>
1010Stability: experimental
1111Homepage: http://haskell-distributed.github.com/
1212Bug-Reports: https://github.com/haskell-distributed/distributed-process/issues
@@ -21,7 +21,7 @@ Description: This is an implementation of Cloud Haskell, as described in
2121
2222 You will probably also want to install a Cloud Haskell backend such
2323 as distributed-process-simplelocalnet.
24−Tested-With: GHC==7.2.2 GHC==7.4.1 GHC==7.4.2 GHC==7.6.2
24+Tested-With: GHC==7.10.3 GHC==8.0.2 GHC==8.2.2 GHC==8.4.4
2525Category: Control
2626extra-source-files: ChangeLog
2727
@@ -34,30 +34,33 @@ flag th
3434 description: Build with Template Haskell support
3535 default: True
3636
37−flag prof
38− description: Compiling with profiling enabled
39− default: False
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
4042
4143Library
42− Build-Depends: base >= 4.4 && < 5,
43− binary >= 0.6.3 && < 0.8,
44+ Build-Depends: base >= 4.6 && < 5,
45+ binary >= 0.6.3 && < 0.10,
4446 hashable >= 1.2.0.5 && < 1.3,
45− network-transport >= 0.4.1.0 && < 0.5,
46− stm >= 2.4 && < 2.5,
47− transformers >= 0.2 && < 0.5,
48− mtl >= 2.0 && < 2.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,
4951 data-accessor >= 0.2 && < 0.3,
5052 bytestring >= 0.9 && < 0.11,
51− old-locale >= 1.0 && < 1.1,
52− time >= 1.2 && < 1.5,
5353 random >= 1.0 && < 1.2,
54− ghc-prim >= 0.2 && < 0.4,
5554 distributed-static >= 0.2 && < 0.4,
56− rank1dynamic >= 0.1 && < 0.3,
57− syb >= 0.3 && < 0.5
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
5860 Exposed-modules: Control.Distributed.Process,
5961 Control.Distributed.Process.Closure,
6062 Control.Distributed.Process.Debug,
63+ Control.Distributed.Process.Internal.BiMultiMap,
6164 Control.Distributed.Process.Internal.Closure.BuiltIn,
6265 Control.Distributed.Process.Internal.Closure.Explicit,
6366 Control.Distributed.Process.Internal.CQueue,
@@ -75,86 +78,91 @@ Library
7578 Control.Distributed.Process.UnsafePrimitives
7679 Control.Distributed.Process.Management.Internal.Agent,
7780 Control.Distributed.Process.Management.Internal.Bus,
78− Control.Distributed.Process.Management.Internal.Table,
7981 Control.Distributed.Process.Management.Internal.Types,
8082 Control.Distributed.Process.Management.Internal.Trace.Primitives,
8183 Control.Distributed.Process.Management.Internal.Trace.Remote,
8284 Control.Distributed.Process.Management.Internal.Trace.Types,
8385 Control.Distributed.Process.Management.Internal.Trace.Tracer
84− -- Extensions: -- we use explicit per-file LANGUAGE pragmas
8586 ghc-options: -Wall
8687 HS-Source-Dirs: src
87− if impl(ghc <= 7.4.2)
88− Build-Depends: containers >= 0.4 && < 0.5,
89− deepseq == 1.3.0.0
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
90110 else
91− Build-Depends: containers >= 0.4 && < 0.6,
92− deepseq >= 1.3.0.1 && < 1.4
111+ Build-Depends: time >= 1.5
93112 if flag(th)
94− if impl(ghc <= 7.4.2)
95− Build-Depends: template-haskell >= 2.7 && < 2.8
96− else
97− Build-Depends: template-haskell >= 2.6 && < 2.10
113+ other-extensions: TemplateHaskell
114+ Build-Depends: template-haskell >= 2.6
98115 Exposed-modules: Control.Distributed.Process.Internal.Closure.TH
99116 CPP-Options: -DTemplateHaskellSupport
100117
101118-- Tests are in distributed-process-test package, for convenience.
102119
103120benchmark distributed-process-throughput
104− Type: exitcode-stdio-1.0
105− Build-Depends: base >= 4.4 && < 5,
121+ Type: exitcode-stdio-1.0
122+ Build-Depends: base >= 4.6 && < 5,
106123 distributed-process,
107− network-transport-tcp >= 0.3 && < 0.5,
124+ network-transport-tcp >= 0.3 && < 0.7,
108125 bytestring >= 0.9 && < 0.11,
109− binary >= 0.6.3 && < 0.8
126+ binary >= 0.6.3 && < 0.10
110127 Main-Is: benchmarks/Throughput.hs
111128 ghc-options: -Wall
112− Extensions: BangPatterns
113129
114130benchmark distributed-process-latency
115131 Type: exitcode-stdio-1.0
116− Build-Depends: base >= 4.4 && < 5,
132+ Build-Depends: base >= 4.6 && < 5,
117133 distributed-process,
118− network-transport-tcp >= 0.3 && < 0.5,
134+ network-transport-tcp >= 0.3 && < 0.7,
119135 bytestring >= 0.9 && < 0.11,
120− binary >= 0.6.3 && < 0.8
136+ binary >= 0.6.3 && < 0.10
121137 Main-Is: benchmarks/Latency.hs
122138 ghc-options: -Wall
123− Extensions: BangPatterns
124139
125140benchmark distributed-process-channels
126141 Type: exitcode-stdio-1.0
127− Build-Depends: base >= 4.4 && < 5,
142+ Build-Depends: base >= 4.6 && < 5,
128143 distributed-process,
129− network-transport-tcp >= 0.3 && < 0.5,
144+ network-transport-tcp >= 0.3 && < 0.7,
130145 bytestring >= 0.9 && < 0.11,
131− binary >= 0.6.3 && < 0.8
146+ binary >= 0.6.3 && < 0.10
132147 Main-Is: benchmarks/Channels.hs
133148 ghc-options: -Wall
134− Extensions: BangPatterns
135149
136150benchmark distributed-process-spawns
137151 Type: exitcode-stdio-1.0
138− Build-Depends: base >= 4.4 && < 5,
152+ Build-Depends: base >= 4.6 && < 5,
139153 distributed-process,
140− network-transport-tcp >= 0.3 && < 0.5,
154+ network-transport-tcp >= 0.3 && < 0.7,
141155 bytestring >= 0.9 && < 0.11,
142− binary >= 0.6.3 && < 0.8
156+ binary >= 0.6.3 && < 0.10
143157 Main-Is: benchmarks/Spawns.hs
144158 ghc-options: -Wall
145− Extensions: BangPatterns
146159
147160benchmark distributed-process-ring
148161 Type: exitcode-stdio-1.0
149− Build-Depends: base >= 4.4 && < 5,
162+ Build-Depends: base >= 4.6 && < 5,
150163 distributed-process,
151− network-transport-tcp >= 0.3 && < 0.5,
164+ network-transport-tcp >= 0.3 && < 0.7,
152165 bytestring >= 0.9 && < 0.11,
153− binary >= 0.6.3 && < 0.8
166+ binary >= 0.6.3 && < 0.10
154167 Main-Is: benchmarks/ProcessRing.hs
155− if flag(prof)
156− ghc-options: -Wall -threaded -prof -auto-all -fno-prof-count-entries -rtsopts
157− else
158− ghc-options: -Wall -threaded -O2 -rtsopts
159− Extensions: BangPatterns,
160− ScopedTypeVariables
168+ ghc-options: -Wall -threaded -O2 -rtsopts
doc/semantics/CloudHaskellSemantics.texnew file+706
@@ -0,0 +1,706 @@
1+\documentclass{article}
2+\usepackage[margin=2.5cm]{geometry}
3+\usepackage{amsmath, amssymb, stmaryrd}
4+\usepackage{mathpazo, times}
5+\usepackage{float}
6+\usepackage{listings}
7+
8+\newcommand{\sLam}[2]{\lambda {#1} \cdot {#2}}
9+\newcommand{\sApp}[2]{{#1} {#2}}
10+\newcommand{\sBind}[2]{{#1} \mathrel{\texttt{>}\!\!\texttt{>}\!\texttt{=}} {#2}}
11+\newcommand{\sReturn}{\mathtt{return}}
12+\newcommand{\sThrow}[1]{\mathtt{throw} \; {#1}}
13+\newcommand{\sCatch}[2]{\mathtt{catch} \; {#1} \; {#2}}
14+
15+\newcommand{\sExpect}{\mathtt{expect}}
16+\newcommand{\sSend}{\mathtt{send}}
17+\newcommand{\sSpawn}{\mathtt{spawn}}
18+\newcommand{\sLink}{\mathtt{link}}
19+\newcommand{\sReconnect}{\mathtt{reconnect}}
20+\newcommand{\sNewChan}{\mathtt{newChan}}
21+\newcommand{\sSendChan}{\mathtt{sendChan}}
22+\newcommand{\sReceiveChan}{\mathtt{receiveChan}}
23+\newcommand{\sMonitor}{\mathtt{monitor}}
24+
25+\DeclareMathOperator{\sNodeOf}{node}
26+\DeclareMathOperator{\sProcessOf}{process}
27+
28+\newcommand{\sSpawned}{\mathtt{spawned}}
29+
30+\newcommand{\sExtend}[1]{\mathrel{\triangleright_{#1}}}
31+
32+\newcommand{\sPar}{\mathrel{\parallel}}
33+\newcommand{\sProc}[2]{{#1}_{#2}}
34+
35+\newcommand{\sNid}{\ensuremath{\mathit{nid}}}
36+\newcommand{\sPid}{\ensuremath{\mathit{pid}}}
37+\newcommand{\sCid}{\ensuremath{\mathit{cid}}}
38+
39+\newcommand{\sId}{\ensuremath{\mathit{id}}}
40+\newcommand{\sRef}{\ensuremath{\mathit{ref}}}
41+
42+\newcommand{\sLinks}{\ensuremath{\mathit{links}}}
43+\newcommand{\sNode}[3]{\left[{#1} ; {#2}\right]_{#3}}
44+\newcommand{\sSystem}[4]{\left\langle #1 ; #2 ; #3 ; #4 \right\rangle}
45+\newcommand{\sNodes}{\mathcal{N}}
46+\newcommand{\sQueue}{\mathcal{Q}}
47+\newcommand{\sProcesses}{\mathcal{P}}
48+\newcommand{\sBlacklist}{\mathcal{B}}
49+\newcommand{\sMonitors}{\mathcal{M}}
50+
51+\newcommand{\sJust}[1]{\mathtt{Just} \; {#1}}
52+\newcommand{\sNothing}{\mathtt{Nothing}}
53+
54+\newcommand{\sCtxt}[1]{\mathbb{#1}}
55+
56+\newcommand{\sSenders}{\mathit{senders}}
57+
58+\newcommand{\OR}{\mathrel{|}}
59+\newcommand{\where}{\mathrel{|}}
60+
61+\floatstyle{boxed}
62+\restylefloat{figure}
63+
64+\lstset{basicstyle=\ttfamily\small}
65+
66+\begin{document}
67+
68+\title{Cloud Haskell Semantics (DRAFT)}
69+\author{Well-Typed LLP}
70+\date{\today}
71+
72+\maketitle
73+
74+\section{Introduction}
75+
76+Cloud Haskell brings Erlang-style concurrency to Haskell in the form of a
77+library.\footnote{\texttt{http://hackage.haskell.org/package/distributed-process}}
78+The original publication \cite{cloudhaskell} is a good introduction to Cloud
79+Haskell, and is accompanied by an ever-growing number of resouces, collected on
80+the Haskell
81+Wiki.\footnote{\texttt{http://www.haskell.org/haskellwiki/Cloud\_Haskell}} The
82+current document augments the original publication about Cloud Haskell by
83+giving a precise semantics to the Cloud Haskell primitives. It is meant as a
84+reference, not an introduction.
85+
86+The original Cloud Haskell paper stipulates that messages are ``asynchronous,
87+reliable, and buffered'', but does not describe how this can be achieved.
88+Understanding ``reliable'' to mean ``reliable ordered'' (or ``TCP-like''),
89+the reliability of message delivery comes from the reliability of the
90+underlying network protocol---up to a point.
91+
92+The problem is that the underlying network protocol is connection-oriented, but
93+Cloud Haskell is not. Intuitively, when $P$ sends a message to $Q$, we open a
94+reliable-ordered connection from $P$ to $Q$. Reliability of message delivery
95+now follows from reliability of the network protocol, until $P$ somehow gets
96+disconnected from $Q$. If $P$ now sends another message to $Q$, the
97+implementation cannot simply reconnect: after all, some messages that were sent
98+on the first connection might not have been delivered. This means that $P$
99+might send $m_1, m_2, m_3$ to $Q$, but $Q$ will receive $m_1,
100+m_3$.\footnote{Indeed, message passing in Erlang is ordered but unreliable for
101+the same reason \cite{erlang}.}
102+
103+One (non-)solution is for $P$ to buffer all messages it sends to $Q$, and
104+remove messages from this buffer only when $Q$ acknowledges that it received
105+them. On a reconnect $P$ must ask which message $Q$ last received, and
106+retransmit the rest. This means that when $P$ gets disconnected from $Q$, it
107+must infinitely buffer all messages sent to $Q$ (until a connection is
108+reestablished). However, infinite buffering is too strong a requirement;
109+moreover, this is unsatisfactory because it means implementing a reliable
110+protocol on top of the underlying reliable network protocol. We would like a
111+different solution.
112+
113+Instead, Cloud Haskell does \emph{not} attempt to reconnect automatically, but
114+provides a $\sReconnect$ primitive which gives programmers the option of
115+reconnecting manually. This is an explicit acknowledgement from the programmer
116+that message loss might occur, and forces them to consider how such loss might
117+be dealt with.
118+
119+The semantics we present is based on \cite{unified}, which we will refer to the
120+as the ``Unified'' semantics. However, we will present the semantics in a more
121+``Haskell'' style following the semantics for STM \cite{stm}. It differs from
122+the Unified semantics in that
123+%
124+\begin{enumerate}
125+\item We introduce an explicit notion of \textit{reconnecting} (with potential
126+message loss)
127+\item We simplify the semantics: we ``flatten'' sets of nodes of processes as
128+sets of processes (but assume a mapping from process identifiers to node
129+identifiers), do not have per-process mailboxes (but only the system queue or
130+``ether'') and do not have an explicit concept of node controllers
131+\end{enumerate}
132+%
133+Our semantics differs from the STM semantics in that we pretend that the Cloud
134+Haskell \texttt{Process} monad is the top-level monad, and do not consider the
135+\texttt{IO} monad at all. Current imprecisions with respect to the ``real''
136+Cloud Haskell are
137+%
138+\begin{enumerate}
139+\item We ignore the issue of serializability, other than to say that the
140+semantics will get stuck when trying to send a non-serializable payload;
141+consequently, we do not formalize $\mathtt{static}$
142+\item We do not formalize all Cloud Haskell primitives (merging of ports,
143+``advanced messaging'', and others)
144+\item Some of the concepts that we do formalize are lower-level concepts; for
145+instance, the primitive $\sSpawn$ that we formalize is asynchronous (following
146+the Unified semantics); a synchronous construct can be derived.
147+\end{enumerate}
148+
149+\section{Preliminaries}
150+
151+Cloud Haskell Processes run on \emph{nodes}. Processes communicate by sending
152+messages to each other (directly or using typed channels). Processes can also
153+send messages to nodes (for instance, a request to spawn or monitor a process).
154+
155+We assume disjoint countable sets $\mathtt{NodeId}$, $\mathtt{ProcessId}$, and
156+$\mathtt{ChannelId}$, changed over by \sNid, \sPid{} and \sCid{} respectively,
157+and representing process identifiers, node identifiers, and (typed) channel
158+identifiers. We assume the existence of total functions
159+%
160+\begin{equation*}
161+\begin{array}{r@{\;:\;}l}
162+\sNodeOf & (\mathtt{ProcessId} \uplus \mathtt{ChannelId}) \rightarrow \mathtt{NodeId} \\
163+\sProcessOf & \mathtt{ChannelId} \rightarrow \mathtt{ProcessId}
164+\end{array}
165+\end{equation*}
166+%
167+and define
168+$$\mathtt{Identifier} = \mathtt{NodeId} \uplus \mathtt{ProcessId} \uplus \mathtt{ChannelId}$$
169+and let $\sId$ range over $\mathtt{Identifier}$.
170+
171+We represent a process as a pair $\sProc{M}{\sPid}$ of a term $M$ and a process
172+ID $\sPid$. We will denote a set of processes as
173+%
174+ $$\sProc{M}{\sPid} \sPar \cdots \sPar \sProc{N}{\sPid'}$$
175+%
176+A \emph{system}
177+ $\sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}$
178+is a tuple containing a set $\sProcesses$ of processes, a \emph{system
179+queue} $\sQueue$, a \emph{blacklist} $\sBlacklist$, and a set of monitors
180+$\sMonitors$.
181+The set of monitors $\sMonitors$ is a set of tuples
182+ $(\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid, \sRef)$
183+which records that node \sNid{} knows that process $\sPid_\mathit{fr}$ is
184+monitoring $\sId_\mathit{to}$ ($\sRef$ is the monitor reference). The system
185+queue is a set of triples $(\sId_\mathit{to}, \sId_\mathit{fr},
186+\mathit{message})$ of messages that have been sent but not yet processed. The
187+blacklist records disconnections and is represented as a of pairs
188+$(\sId_\mathit{to}, \sId_\mathit{fr})$.
189+
190+\section{Semantics}
191+
192+We follow the STM semantics as closely as possible. The language is the same
193+except for the primitives considered, and we use the same concept of evaluation
194+contexts:
195+
196+\begin{equation*}
197+\begin{array}{llll}
198+\text{value} & V & ::= & \sId \OR
199+ \sLam{x}{M} \OR
200+ \sReturn \; M \OR
201+ \sBind{M}{N} \OR
202+ \sThrow{M} \OR \sCatch{M}{N} \OR \\
203+ &&& \sExpect \OR
204+ \sSend \; \sPid \; M \OR
205+ \sSpawn \; \sNid \; M \OR
206+ \sMonitor \; \sId
207+\\
208+\text{term} & M, N & ::= & x \OR
209+ V \OR
210+ \sApp{M}{N} \OR
211+ \cdots \\
212+\\
213+\text{Evaluation} & \sCtxt{E} & ::= & [] \OR \sBind{\sCtxt{E}}{M} \OR \sCatch{\sCtxt{E}}{M} \\
214+\text{context} & \sCtxt{P}_\sPid & ::= & \sCtxt{E}[]_\sPid \sPar \sProcesses
215+\end{array}
216+\end{equation*}
217+%
218+Indeed, the ``administrative'' transitions are identical
219+(Figure~\ref{fig:administrative}).
220+
221+\begin{figure}
222+\small
223+\begin{equation*}
224+\frac{
225+ \llbracket M \rrbracket = V \qquad
226+ M \neq V
227+}{
228+ M \rightarrow V
229+} \textsc{Eval}
230+\qquad
231+\frac{
232+ M \rightarrow N
233+}{
234+ \sSystem{\sCtxt{P}[M]_\sPid}
235+ {\sQueue}
236+ {\sBlacklist}
237+ {\sMonitors}
238+\rightarrow
239+ \sSystem{\sCtxt{P}[N]_\sPid}
240+ {\sQueue}
241+ {\sBlacklist}
242+ {\sMonitors}
243+} \textsc{Admin}
244+\end{equation*}
245+%
246+\begin{equation*}
247+\begin{array}{r@{\;\rightarrow\;}ll@{\hspace{2em}}r@{\;\rightarrow\;}ll}
248+\sBind{\sReturn \; N}{M} & M \; N & \textsc{Bind} & \sCatch{(\sReturn \; M)}{N} & \sReturn \; M & \textsc{Catch}_1 \\
249+\sBind{\sThrow{N}}{M} & \sThrow{N} & \textsc{Throw} & \sCatch{(\sThrow M)}{N} & N \; M & \textsc{Catch}_2 \\
250+\end{array}
251+\end{equation*}
252+%
253+\caption{\label{fig:administrative}Administrative Transitions}
254+\end{figure}
255+
256+\subsection{Disconnect and Reconnect}
257+
258+Figure~\ref{fig:disconnect} gives the semantics for disconnecting and
259+reconnecting. Rule \textsc{Disconnect} models random network disconnect
260+between nodes $\sNid_1$ and $\sNid_2$. We assume that entire \emph{nodes} get
261+disconnected from each other, not individual processes. \emph{Reconnecting}
262+however is on a per connection basis (\textsc{Recon-Ex}). Connections to and
263+from node controllers can be implicitly reconnected ($\textsc{Recon-Im}_1$ and
264+$\textsc{Recon-Im}_2$).
265+
266+\begin{figure}
267+\small
268+\begin{equation*}
269+\frac{
270+ \sNid_1 \neq \sNid_2
271+}{
272+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}
273+\rightarrow
274+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist \cup (\overline{\sNid_1} \times \overline{\sNid_2})}{\sMonitors}
275+} \textsc{Disconnect}
276+\end{equation*}
277+%
278+\begin{equation*}
279+\frac{
280+}{
281+ \sSystem{\sCtxt{P}[ \sReconnect \; \sId_\mathit{to} ]_{\sPid_\mathit{fr}}}
282+ {\sQueue}
283+ {\sBlacklist}
284+ {\sMonitors}
285+\rightarrow
286+ \sSystem{\sCtxt{P}[ \sReturn \; () ]_\sPid}
287+ {\sQueue}
288+ {\sBlacklist \backslash (\sId_\mathit{to}, \sPid_\mathit{fr})}
289+ {\sMonitors}
290+} \textsc{Recon-Ex}
291+\end{equation*}
292+%
293+\begin{equation*}
294+\frac{
295+}{
296+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist, (\sNid_\mathit{to}, \sId_\mathit{fr})}{\sMonitors}
297+\rightarrow
298+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}
299+} \textsc{Recon-Im}_1
300+\qquad
301+\frac{
302+}{
303+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist, (\sId_\mathit{to}, \sNid_\mathit{fr})}{\sMonitors}
304+\rightarrow
305+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}
306+} \textsc{Recon-Im}_2
307+\end{equation*}
308+%
309+where
310+%
311+\begin{equation*}
312+\overline{\sNid} \subset \mathtt{Identifier}
313+ = \{ \sNid \} \cup
314+ \{ \sPid \where \sNodeOf(\sPid) = \sNid \} \cup
315+ \{ \sCid \where \sNodeOf(\sCid) = \sNid \}
316+\end{equation*}
317+\caption{\label{fig:disconnect}Disconnect and Reconnect}
318+\end{figure}
319+
320+\subsection{Communication}
321+
322+The semantics for the basic primitives are listed in Figure~\ref{fig:basic}.
323+Once a connection has been blacklisted, no further messages can be sent across
324+that connection (until an explicit or implicit reconnect).
325+
326+Message passing is ordered but only for a given sender and receiver; no
327+ordering guarantees exist between messages sent by different processes. For
328+that reason rules \textsc{Expect}, \textsc{ReceiveChan} and \textsc{Spawn-Exec}
329+choose the \emph{first} message of a \emph{randomly chosen} sender.
330+
331+Since the semantics is not type-driven, we represent typed channels simply as
332+an identifier with an annotation whether it is the send-end ($\sCid^s$) or the
333+receive end ($\sCid^r$) of the channel (rules \textsc{NewChan},
334+\textsc{SendChan} and \textsc{ReceiveChan}).
335+
336+Spawning finally is asynchronous. When process $P$ spawns process $Q$ on node
337+$\sNid$ (rule \textsc{Spawn-Async}) a message is sent to node $\sNid$ (which
338+may, of course, never arrive). When the remote node receives the message and
339+actually spawns $Q$ (rule \textsc{Spawn-Exec}) it sends a message back to $P$
340+with $Q$'s process ID. In Cloud Haskell this primitive is called
341+\texttt{spawnAsync}, and the Cloud Haskell \texttt{spawn} primitive is defined
342+in terms of \texttt{spawnAsync} (we do not consider the synchronous version in
343+this document).
344+
345+\begin{figure}
346+\small
347+\begin{equation*}
348+\frac{
349+}{
350+ \sSystem{\sCtxt{P}[ \sSend \; \sPid_\mathit{to} \; M ]_{\sPid_\mathit{fr}}}
351+ {\sQueue}
352+ {\sBlacklist}
353+ {\sMonitors}
354+\rightarrow
355+ \sSystem{\sCtxt{P}[ \sReturn \; () ]_{\sPid_\mathit{fr}}}
356+ {\sQueue \sExtend{\sBlacklist} (\sPid_\mathit{to}, \sPid_\mathit{fr}, M)}
357+ {\sBlacklist}
358+ {\sMonitors}
359+} \textsc{Send}
360+\end{equation*}
361+%
362+\begin{equation*}
363+\frac{
364+ \sId_{\mathit{fr}} \notin \sSenders(\sQueue)
365+}{
366+ \sSystem{\sCtxt{P}[ \sExpect ]_{\sPid_\mathit{to}}}
367+ {\sQueue, (\sPid_\mathit{to}, \sId_\mathit{fr}, M), \sQueue'}
368+ {\sBlacklist}
369+ {\sMonitors}
370+\rightarrow
371+ \sSystem{\sCtxt{P}[ \sReturn \; M ]_{\sPid_\mathit{to}}}
372+ {\sQueue, \sQueue'}
373+ {\sBlacklist}
374+ {\sMonitors}
375+} \textsc{Expect}
376+\end{equation*}
377+%
378+\begin{equation*}
379+\frac{
380+ \sCid \text{ fresh}
381+\qquad
382+ \sProcessOf(\sCid) = \sPid
383+}{
384+ \sSystem{\sCtxt{P}[\sNewChan]_\sPid}
385+ {\sQueue}
386+ {\sBlacklist}
387+ {\sMonitors}
388+\rightarrow
389+ \sSystem{\sCtxt{P}[\sReturn \; (\sCid^s, \sCid^r)]_\sPid}
390+ {\sQueue}
391+ {\sBlacklist}
392+ {\sMonitors}
393+} \textsc{NewChan}
394+\end{equation*}
395+
396+\begin{equation*}
397+\frac{
398+}{
399+ \sSystem{\sCtxt{P}[ \sSendChan \; \sCid_\mathit{to}^s \; M ]_{\sPid_\mathit{fr}}}
400+ {\sQueue}
401+ {\sBlacklist}
402+ {\sMonitors}
403+\rightarrow
404+ \sSystem{\sCtxt{P}[ \sReturn \; () ]_{\sPid_\mathit{fr}}}
405+ {\sQueue \sExtend{\sBlacklist} (\sCid_\mathit{to}, \sPid_\mathit{fr}, M)}
406+ {\sBlacklist}
407+ {\sMonitors}
408+} \textsc{SendChan}
409+\end{equation*}
410+
411+\begin{equation*}
412+\frac{
413+ \sPid_{\mathit{fr}} \notin \sSenders(\sQueue)
414+}{
415+ \sSystem{\sCtxt{P}[ \sReceiveChan \; \sCid_\mathit{to}^r ]_{\sPid}}
416+ {\sQueue, (\sCid_\mathit{to}, \sPid_\mathit{fr}, M), \sQueue'}
417+ {\sBlacklist}
418+ {\sMonitors}
419+\rightarrow
420+ \sSystem{\sCtxt{P}[ \sReturn \; M ]_{\sPid}}
421+ {\sQueue, \sQueue'}
422+ {\sBlacklist}
423+ {\sMonitors}
424+} \textsc{ReceiveChan}
425+\end{equation*}
426+%
427+\begin{equation*}
428+\frac{
429+ \sRef \text{ fresh}
430+}{
431+ \sSystem{\sCtxt{P}[ \sSpawn \; \sNid \; M ]_\sPid}
432+ {\sQueue}
433+ {\sBlacklist}
434+ {\sMonitors}
435+\rightarrow
436+ \sSystem{\sCtxt{P}[ \sReturn \; \sRef ]_\sPid}
437+ {\sQueue \sExtend{\sBlacklist} (\sNid, \sPid, \sSpawn \; \sRef \; M)}
438+ {\sBlacklist}
439+ {\sMonitors}
440+} \textsc{Spawn-Async}
441+\end{equation*}
442+%
443+\begin{equation*}
444+\frac{
445+ \sPid \notin \sSenders(\sQueue)
446+\qquad
447+ \sPid' \text{ fresh}
448+\qquad
449+ \sNodeOf(\sPid') = \sNid
450+}{
451+ \sSystem{\sProcesses}
452+ {\sQueue, (\sNid, \sPid, \sSpawn \; \sRef \; M), \sQueue'}
453+ {\sBlacklist}
454+ {\sMonitors}
455+\rightarrow
456+ \sSystem{\sProcesses \sPar M_{\sPid'}}
457+ {\sQueue, \sQueue' \sExtend{\sBlacklist} (\sPid, \sNid, \sSpawned \; \sRef \; \sPid')}
458+ {\sBlacklist}
459+ {\sMonitors}
460+} \textsc{Spawn-Exec}
461+\end{equation*}
462+%
463+where
464+%
465+\begin{equation*}
466+\begin{array}{l@{\;=\;}l@{\hspace{3em}}l}
467+ \sQueue \sExtend{\sBlacklist} (\sId_\mathit{to}, \sId_\mathit{fr}, M)
468+&
469+ \sQueue, (\sId_\mathit{to}, \sId_\mathit{fr}, M)
470+&
471+ \text{if } (\sId_\mathit{to}, \sId_\mathit{fr}) \notin \sBlacklist
472+\\
473+ \sQueue \sExtend{\sBlacklist} (\sId_\mathit{to}, \sId_\mathit{fr}, M)
474+&
475+ \sQueue
476+&
477+ \text{otherwise}
478+\end{array}
479+\end{equation*}
480+%
481+$(\sExtend{\sBlacklist})$ is only defined for serializable payloads.
482+\caption{\label{fig:basic}Basic Primitives}
483+\end{figure}
484+
485+\subsection{Monitoring}
486+
487+When process $P$ on node $\sNid_P$ wants to monitor process $Q$ on node
488+$\sNid_Q$ \emph{both} nodes must be notified. The local node it notified
489+immediately, and a message is sent to to the remote node (rules
490+\textsc{Mon-Loc} and \textsc{Mon-Rem}). As with all messages, the
491+message to the remote node might be lost. When $P$ is disconnected from $Q$ it
492+is the responsibility of $P$'s local node $\sNid_P$ to notify $P$
493+(\textsc{Mon-Dis}); when $Q$ terminates (\textsc{Mon-Ret}) or crashes
494+(\textsc{Mon-Throw}) it is the responsibility of the remote node $\sNid_Q$ to
495+notify $P$.
496+
497+\begin{figure}
498+\small
499+\begin{equation*}
500+\frac{
501+ \sNid_\mathit{fr} = \sNodeOf(\sPid_\mathit{fr})
502+\qquad
503+ \sNid_\mathit{to} = \sNodeOf(\sId_\mathit{to})
504+\qquad
505+ \sRef \text{ fresh}
506+}{
507+\sSystem{\sCtxt{P}[ \sMonitor \; \sId_\mathit{to} ]_{\sPid_\mathit{fr}}}
508+ {\sQueue}
509+ {\sBlacklist}
510+ {\sMonitors}
511+\rightarrow
512+ \sSystem{\sCtxt{P}[ \sReturn \; \sRef ]_{\sPid_\mathit{fr}}}
513+ {\sQueue \sExtend{\sBlacklist} (\sNid_\mathit{to}, \sPid_\mathit{fr}, \sMonitor \; \sRef \; \sId_\mathit{to})}
514+ {\sBlacklist}
515+ {\sMonitors, (\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid_\mathit{fr}, \sRef)}
516+} \textsc{Mon-Loc}
517+\end{equation*}
518+%
519+\begin{equation*}
520+\frac{
521+ \sPid_\mathit{fr} \notin \sSenders(\sQueue)
522+}{
523+ \sSystem{\sProcesses}
524+ {\sQueue, (\sNid_\mathit{to}, \sPid_\mathit{fr}, \sMonitor \; \sRef \; \sId_\mathit{to}), \sQueue'}
525+ {\sBlacklist}
526+ {\sMonitors}
527+\rightarrow
528+ \sSystem{\sProcesses}
529+ {\sQueue, \sQueue'}
530+ {\sBlacklist}
531+ {\sMonitors, (\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid_\mathit{to}, \sRef)}
532+} \textsc{Mon-Rem}
533+\end{equation*}
534+%
535+\begin{equation*}
536+\frac{
537+ \sNid_\mathit{fr} = \sNodeOf(\sPid_\mathit{fr})
538+\qquad
539+ (\sId_\mathit{to}, \sPid_\mathit{fr}) \in \sBlacklist
540+}{
541+ \sSystem{\sProcesses}
542+ {\sQueue}
543+ {\sBlacklist}
544+ {\sMonitors, (\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid_\mathit{fr}, \sRef)}
545+\rightarrow
546+ \sSystem{\sProcesses}
547+ {\sQueue, (\sPid_\mathit{fr}, \sId_\mathit{to}, \mathtt{discon} \; \sRef)}
548+ {\sBlacklist}
549+ {\sMonitors}
550+} \textsc{Mon-Dis}
551+\end{equation*}
552+%
553+\begin{equation*}
554+\frac{
555+ \sNodeOf(\sPid) = \sNid
556+}{
557+ \sSystem{{(\sReturn \; ())}_\sPid \sPar \sProcesses}
558+ {\sQueue}
559+ {\sBlacklist}
560+ {\sMonitors, (\sPid, \sPid', \sNid, \sRef)}
561+\rightarrow
562+ \sSystem{{(\sReturn \; ())}_\sPid \sPar \sProcesses}
563+ {\sQueue \sExtend{\sBlacklist} (\sPid', \sNid, \mathtt{died} \; \sRef)}
564+ {\sBlacklist}
565+ {\sMonitors}
566+} \textsc{Mon-Ret}
567+\end{equation*}
568+%
569+\begin{equation*}
570+\frac{
571+ \sNodeOf(\sPid) = \sNid
572+}{
573+ \sSystem{{(\sThrow{M})}_\sPid \sPar \sProcesses}
574+ {\sQueue}
575+ {\sBlacklist}
576+ {\sMonitors, (\sPid, \sPid', \sNid, \sRef)}
577+\rightarrow
578+ \sSystem{{(\sThrow{M})}_\sPid \sPar \sProcesses}
579+ {\sQueue \sExtend{\sBlacklist} (\sPid', \sNid, \mathtt{exc} \; \sRef \; M)}
580+ {\sBlacklist}
581+ {\sMonitors}
582+} \textsc{Mon-Throw}
583+\end{equation*}
584+%
585+\begin{equation*}
586+\frac{
587+ \sNodeOf(\sPid) = \sNid
588+\qquad
589+ \sPid \notin \sProcesses
590+}{
591+ \sSystem{\sProcesses}
592+ {\sQueue}
593+ {\sBlacklist}
594+ {\sMonitors, (\sPid, \sPid', \sNid, \sRef)}
595+\rightarrow
596+ \sSystem{\sProcesses}
597+ {\sQueue \sExtend{\sBlacklist} (\sPid', \sNid, \mathtt{unknown} \; \sRef)}
598+ {\sBlacklist}
599+ {\sMonitors}
600+} \textsc{Mon-Unknown}
601+\end{equation*}
602+%
603+\caption{Monitoring}
604+\end{figure}
605+
606+\subsection{Process Termination}
607+
608+The rules for normal and abnormal process termination are defined in
609+Figure~\ref{fig:termination}. When a process crashes it dies
610+silently, unless monitors are setup.
611+
612+\begin{figure}
613+\small
614+%
615+\begin{equation*}
616+\frac{
617+ \sNodeOf(\sPid) = \sNid
618+\qquad
619+ \nexists \; \sPid', \sRef \cdot (\sPid', \sPid, \sNid, \sRef) \in \sMonitors
620+}{
621+ \sSystem{{(\sReturn \; ())}_\sPid \sPar \sProcesses}
622+ {\sQueue}
623+ {\sBlacklist}
624+ {\sMonitors}
625+\rightarrow
626+ \sSystem{\sProcesses}
627+ {\sQueue}
628+ {\sBlacklist}
629+ {\sMonitors}
630+} \textsc{TermRet}
631+\end{equation*}
632+%
633+\begin{equation*}
634+\frac{
635+ \sNodeOf(\sPid) = \sNid
636+\qquad
637+ \nexists \; \sPid', \sRef \cdot (\sPid', \sPid, \sNid, \sRef) \in \sMonitors
638+}{
639+ \sSystem{{(\sThrow{M})}_\sPid \sPar \sProcesses}
640+ {\sQueue}
641+ {\sBlacklist}
642+ {\sMonitors}
643+\rightarrow
644+ \sSystem{\sProcesses}
645+ {\sQueue}
646+ {\sBlacklist}
647+ {\sMonitors}
648+} \textsc{TermThrow}
649+\end{equation*}
650+%
651+\caption{\label{fig:termination}Process Termination}
652+\end{figure}
653+
654+\section{Open Issues}
655+
656+\subsection{Ordering of Monitor/Link Notifications}
657+
658+The semantics as described above, like the original Unified semantics, does not
659+guarantee that messages send from process $P$ to process $Q$ (\textsc{Send})
660+are ordered with respect to the link or monitor notification sent when process
661+$P$ terminates normally or abnormally (\textsc{Mon-Ret}, \textsc{Mon-Throw}). This means that if process $P$ does
662+
663+\begin{lstlisting}
664+receiveWait [
665+ match $ \(Reply reply) -> ...
666+ , match $ \(ProcessMonitorNotification ..) -> ...
667+ ]
668+\end{lstlisting}
669+
670+and process $Q$ does
671+
672+\begin{lstlisting}
673+send pidA (Reply reply)
674+// terminate or indeed throw an exception
675+\end{lstlisting}
676+
677+then the semantics does not guarantee that the reply from process $Q$ will
678+arrive at process $P$ before the monitor notification; hence, this results in
679+an (artificial) race condition in process $P$.
680+
681+One possible solution is to regard such a link notification as a message from
682+process $Q$ to process $P$, which should be ordered along with the other
683+messages.
684+
685+\subsection{Ordering and Typed Channels}
686+
687+The situation is more tricky still than sketched above because we of typed
688+channels. The semantics does not provide ordering guarantees between messages
689+sent directly to the process and messages sent on typed channels. That means
690+that even if we consider a link or monitor notification as a message sent to
691+the process, to be ordered with other messages sent to that process, it is
692+still unordered with respect to messages sent on typed channels. This means
693+that we have similar race conditions\footnote{Even ignoring the fact that we
694+currently don't even provide a way to wait for a message on a typed channel
695+\emph{or} a direct message.}
696+
697+A possible solution is to insist that \emph{all} messages from process $P$ to
698+process $Q$ are ordered, no matter how they are sent. From a implementation
699+point of view, this would entail the use of a single ordered network connection
700+for all messages from $P$ to $Q$, rather than using an ordered connection per
701+typed channel plus one for direct messages.
702+
703+\bibliographystyle{apalike}
704+\bibliography{references}
705+
706+\end{document}
doc/semantics/Makefilenew file+11
@@ -0,0 +1,11 @@
1+CloudHaskellSemantics.pdf: *.tex *.bib
2+ pdflatex CloudHaskellSemantics
3+ bibtex CloudHaskellSemantics
4+ pdflatex CloudHaskellSemantics
5+ pdflatex CloudHaskellSemantics
6+
7+.PHONY: clean
8+clean:
9+ rm -f *.aux *.bbl *.blg *.log
10+
11+# vi:set noexpandtab:
doc/semantics/references.bibnew file+72
@@ -0,0 +1,72 @@
1+@inproceedings{cloudhaskell,
2+ author = {Epstein, Jeff and Black, Andrew P. and Peyton-Jones, Simon},
3+ title = {Towards {H}askell in the cloud},
4+ booktitle = {Proceedings of the 4th ACM symposium on Haskell},
5+ series = {Haskell '11},
6+ year = {2011},
7+ isbn = {978-1-4503-0860-1},
8+ location = {Tokyo, Japan},
9+ pages = {118--129},
10+ numpages = {12},
11+ url = {http://doi.acm.org/10.1145/2034675.2034690},
12+ doi = {10.1145/2034675.2034690},
13+ acmid = {2034690},
14+ publisher = {ACM},
15+ address = {New York, NY, USA},
16+ keywords = {erlang, haskell, message-passing},
17+}
18+
19+@inproceedings{unified,
20+ author = {Svensson, Hans and Fredlund, Lars-{\AA}ke and Benac Earle, Clara},
21+ title = {A unified semantics for future {E}rlang},
22+ booktitle = {Proceedings of the 9th ACM SIGPLAN workshop on Erlang},
23+ series = {Erlang '10},
24+ year = {2010},
25+ isbn = {978-1-4503-0253-1},
26+ location = {Baltimore, Maryland, USA},
27+ pages = {23--32},
28+ numpages = {10},
29+ url = {http://doi.acm.org/10.1145/1863509.1863514},
30+ doi = {10.1145/1863509.1863514},
31+ acmid = {1863514},
32+ publisher = {ACM},
33+ address = {New York, NY, USA},
34+ keywords = {erlang, semantics},
35+}
36+
37+@article{stm,
38+ author = {Harris, Tim and Marlow, Simon and Jones, Simon Peyton and Herlihy, Maurice},
39+ title = {Composable memory transactions},
40+ journal = {Commun. ACM},
41+ issue_date = {August 2008},
42+ volume = {51},
43+ number = {8},
44+ month = aug,
45+ year = {2008},
46+ issn = {0001-0782},
47+ pages = {91--100},
48+ numpages = {10},
49+ url = {http://doi.acm.org/10.1145/1378704.1378725},
50+ doi = {10.1145/1378704.1378725},
51+ acmid = {1378725},
52+ publisher = {ACM},
53+ address = {New York, NY, USA},
54+}
55+
56+@inproceedings{erlang,
57+ author = {Svensson, Hans and Fredlund, Lars-\AAke},
58+ title = {A more accurate semantics for distributed erlang},
59+ booktitle = {Proceedings of the 2007 SIGPLAN workshop on ERLANG Workshop},
60+ series = {ERLANG '07},
61+ year = {2007},
62+ isbn = {978-1-59593-675-2},
63+ location = {Freiburg, Germany},
64+ pages = {43--54},
65+ numpages = {12},
66+ url = {http://doi.acm.org/10.1145/1292520.1292528},
67+ doi = {10.1145/1292520.1292528},
68+ acmid = {1292528},
69+ publisher = {ACM},
70+ address = {New York, NY, USA},
71+ keywords = {distributed systems, erlang, model checking, semantics, verification},
72+}
src/Control/Distributed/Process.hs+53−19
@@ -25,6 +25,7 @@ module Control.Distributed.Process
2525 , liftIO -- Reexported for convenience
2626 -- * Basic messaging
2727 , send
28+ , usend
2829 , expect
2930 , expectTimeout
3031 -- * Channels
@@ -39,8 +40,10 @@ module Control.Distributed.Process
3940 , mergePortsRR
4041 -- * Unsafe messaging variants
4142 , unsafeSend
43+ , unsafeUSend
4244 , unsafeSendChan
4345 , unsafeNSend
46+ , unsafeNSendRemote
4447 , unsafeWrapMessage
4548 -- * Advanced messaging
4649 , Match
@@ -64,6 +67,7 @@ module Control.Distributed.Process
6467 , handleMessage_
6568 , handleMessageIf_
6669 , forward
70+ , uforward
6771 , delegate
6872 , relay
6973 , proxy
@@ -98,6 +102,7 @@ module Control.Distributed.Process
98102 , monitorPort
99103 , unmonitor
100104 , withMonitor
105+ , withMonitor_
101106 , MonitorRef -- opaque
102107 , ProcessLinkException(..)
103108 , NodeLinkException(..)
@@ -155,14 +160,16 @@ module Control.Distributed.Process
155160 , reconnectPort
156161 ) where
157162
158−#if ! MIN_VERSION_base(4,6,0)
159−import Prelude hiding (catch)
160−#endif
161−
162163import Control.Monad.IO.Class (liftIO)
163−import Control.Applicative ((<$>))
164+import Control.Applicative
164165import Control.Monad.Reader (ask)
165−import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
166+import Control.Concurrent (killThread)
167+import Control.Concurrent.MVar
168+ ( MVar
169+ , newEmptyMVar
170+ , takeMVar
171+ , putMVar
172+ )
166173import Control.Distributed.Static
167174 ( Closure
168175 , closure
@@ -191,11 +198,13 @@ import Control.Distributed.Process.Internal.Types
191198 , RegisterReply(..)
192199 , LocalProcess(processNode)
193200 , Message
201+ , localProcessWithId
194202 )
195203import Control.Distributed.Process.Serializable (Serializable)
196204import Control.Distributed.Process.Internal.Primitives
197205 ( -- Basic messaging
198206 send
207+ , usend
199208 , expect
200209 -- Channels
201210 , newChan
@@ -204,8 +213,10 @@ import Control.Distributed.Process.Internal.Primitives
204213 , mergePortsBiased
205214 , mergePortsRR
206215 , unsafeSend
216+ , unsafeUSend
207217 , unsafeSendChan
208218 , unsafeNSend
219+ , unsafeNSendRemote
209220 -- Advanced messaging
210221 , Match
211222 , receiveWait
@@ -228,6 +239,7 @@ import Control.Distributed.Process.Internal.Primitives
228239 , handleMessage_
229240 , handleMessageIf_
230241 , forward
242+ , uforward
231243 , delegate
232244 , relay
233245 , proxy
@@ -258,6 +270,7 @@ import Control.Distributed.Process.Internal.Primitives
258270 , monitorPort
259271 , unmonitor
260272 , withMonitor
273+ , withMonitor_
261274 -- Logging
262275 , say
263276 -- Registry
@@ -294,6 +307,10 @@ import Control.Distributed.Process.Internal.Primitives
294307 , reconnectPort
295308 )
296309import Control.Distributed.Process.Node (forkProcess)
310+import Control.Distributed.Process.Internal.Types
311+ ( processThread
312+ , withValidLocalState
313+ )
297314import Control.Distributed.Process.Internal.Spawn
298315 ( -- Spawning Processes/Channels
299316 spawn
@@ -303,7 +320,17 @@ import Control.Distributed.Process.Internal.Spawn
303320 , spawnSupervised
304321 , call
305322 )
306−import Control.Exception (SomeException, throwIO)
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+
307334
308335-- INTERNAL NOTES
309336--
@@ -399,15 +426,22 @@ spawnChannelLocal proc = do
399426-- messages sent to the caller process, and also allows silently dropping late
400427-- or duplicate messages sent to the isolated process after it exits.
401428-- Silently dropping messages may not always be the best approach.
402−callLocal ::
403− Process a -- ^ Process to run
404− -> Process a -- ^ Value returned
405−callLocal proc = do
406− mv <- liftIO newEmptyMVar
407− self <- getSelfPid
408− _ <- spawnLocal $ do
409− link self
410− try proc >>= liftIO . putMVar mv
411− liftIO $ takeMVar mv >>=
412− either (throwIO :: SomeException -> IO a) return
413−
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/Debug.hs+10−14
@@ -83,13 +83,13 @@
8383-- environment variable accepts the following flags, which enable tracing specific
8484-- event types:
8585--
86−-- p = trace the spawning of new processes
87−-- d = trace the death of processes
88−-- n = trace registration of names (i.e., named processes)
89−-- u = trace un-registration of names (i.e., named processes)
90−-- s = trace the sending of messages to other processes
91−-- r = trace the receipt of messages from other processes
92−-- l = trace node up/down events
86+-- * @p@ = trace the spawning of new processes
87+-- * @d@ = trace the death of processes
88+-- * @n@ = trace registration of names (i.e., named processes)
89+-- * @u@ = trace un-registration of names (i.e., named processes)
90+-- * @s@ = trace the sending of messages to other processes
91+-- * @r@ = trace the receipt of messages from other processes
92+-- * @l@ = trace node up/down events
9393--
9494-- Users of the /simplelocalnet/ Cloud Haskell backend should also note that
9595-- because the trace file option only supports trace output from a single node
@@ -141,17 +141,14 @@ module Control.Distributed.Process.Debug
141141 )
142142 where
143143
144−import Control.Applicative ((<$>))
144+import Control.Applicative
145145import Control.Distributed.Process.Internal.Primitives
146146 ( proxy
147− , finally
148147 , die
149148 , whereis
150149 , send
151150 , receiveWait
152151 , matchIf
153− , finally
154− , try
155152 , monitor
156153 )
157154import Control.Distributed.Process.Internal.Types
@@ -196,12 +193,11 @@ import Control.Exception (SomeException)
196193
197194import Control.Monad.IO.Class (liftIO)
198195import Control.Monad.Reader (ask)
196+import Control.Monad.Catch (finally, try)
199197
200198import Data.Binary()
201199
202−#if ! MIN_VERSION_base(4,6,0)
203−import Prelude hiding (catch)
204−#endif
200+import Prelude
205201
206202--------------------------------------------------------------------------------
207203-- Debugging/Tracing API --
src/Control/Distributed/Process/Internal/BiMultiMap.hsnew file+135
@@ -0,0 +1,135 @@
1+-- | This is an implementation of bidirectional multimaps.
2+module Control.Distributed.Process.Internal.BiMultiMap
3+ ( BiMultiMap
4+ , empty
5+ , singleton
6+ , size
7+ , insert
8+ , lookupBy1st
9+ , lookupBy2nd
10+ , delete
11+ , deleteAllBy1st
12+ , deleteAllBy2nd
13+ , partitionWithKeyBy1st
14+ , partitionWithKeyBy2nd
15+ , flip
16+ ) where
17+
18+import Data.List (foldl')
19+import Data.Map.Strict (Map)
20+import qualified Data.Map.Strict as Map
21+import Data.Set (Set)
22+import qualified Data.Set as Set
23+import Prelude hiding (flip, lookup)
24+
25+-- | A bidirectional multimaps @BiMultiMap a b v@ is a set of triplets of type
26+-- @(a, b, v)@.
27+--
28+-- It is possible to lookup values by using either @a@ or @b@ as keys.
29+--
30+data BiMultiMap a b v = BiMultiMap !(Map a (Set (b, v))) !(Map b (Set (a, v)))
31+
32+-- The bidirectional multimap is implemented with a pair of multimaps.
33+--
34+-- Each multimap represents a set of triples, and one invariant is that both
35+-- multimaps should represent exactly the same set of triples.
36+--
37+-- Each of the multimaps, however, uses a different component of the triplets
38+-- as key. This allows to do efficient deletions by any of the two components.
39+
40+-- | The empty bidirectional multimap.
41+empty :: BiMultiMap a b v
42+empty = BiMultiMap Map.empty Map.empty
43+
44+-- | A bidirectional multimap containing a single triplet.
45+singleton :: (Ord a, Ord b, Ord v) => a -> b -> v -> BiMultiMap a b v
46+singleton a b v = insert a b v empty
47+
48+-- | Yields the amount of triplets in the multimap.
49+size :: BiMultiMap a b v -> Int
50+size (BiMultiMap m _) = foldl' (+) 0 $ map Set.size $ Map.elems m
51+
52+-- | Inserts a triplet in the multimap.
53+insert :: (Ord a, Ord b, Ord v)
54+ => a -> b -> v -> BiMultiMap a b v -> BiMultiMap a b v
55+insert a b v (BiMultiMap m r) =
56+ BiMultiMap (Map.insertWith (\_new old -> Set.insert (b, v) old)
57+ a
58+ (Set.singleton (b, v))
59+ m)
60+ (Map.insertWith (\_new old -> Set.insert (a, v) old)
61+ b
62+ (Set.singleton (a, v))
63+ r)
64+
65+-- | Looks up all the triplets whose first component is the given value.
66+lookupBy1st :: Ord a => a -> BiMultiMap a b v -> Set (b, v)
67+lookupBy1st a (BiMultiMap m _) = maybe Set.empty id $ Map.lookup a m
68+
69+-- | Looks up all the triplets whose second component is the given value.
70+lookupBy2nd :: Ord b => b -> BiMultiMap a b v -> Set (a, v)
71+lookupBy2nd b = lookupBy1st b . flip
72+
73+-- | Deletes a triplet. It yields the original multimap if the triplet is
74+-- not present.
75+delete :: (Ord a, Ord b, Ord v)
76+ => a -> b -> v -> BiMultiMap a b v -> BiMultiMap a b v
77+delete a b v (BiMultiMap m r) =
78+ let m' = Map.update (nothingWhen Set.null . Set.delete (b, v)) a m
79+ r' = Map.update (nothingWhen Set.null . Set.delete (a, v)) b r
80+ in BiMultiMap m' r'
81+
82+-- | Deletes all triplets whose first component is the given value.
83+deleteAllBy1st :: (Ord a, Ord b, Ord v) => a -> BiMultiMap a b v -> BiMultiMap a b v
84+deleteAllBy1st a (BiMultiMap m r) =
85+ let (mm, m') = Map.updateLookupWithKey (\_ _ -> Nothing) a m
86+ r' = case mm of
87+ Nothing -> r
88+ Just mb -> reverseDelete a (Set.toList mb) r
89+ in BiMultiMap m' r'
90+
91+-- | Like 'deleteAllBy1st' but deletes by the second component of the triplets.
92+deleteAllBy2nd :: (Ord a, Ord b, Ord v)
93+ => b -> BiMultiMap a b v -> BiMultiMap a b v
94+deleteAllBy2nd b = flip . deleteAllBy1st b . flip
95+
96+-- | Yields the triplets satisfying the given predicate, and a multimap
97+-- with all this triplets removed.
98+partitionWithKeyBy1st :: (Ord a, Ord b, Ord v)
99+ => (a -> Set (b, v) -> Bool) -> BiMultiMap a b v
100+ -> (Map a (Set (b, v)), BiMultiMap a b v)
101+partitionWithKeyBy1st p (BiMultiMap m r) =
102+ let (m0, m1) = Map.partitionWithKey p m
103+ r1 = foldl' (\rr (a, mb) -> reverseDelete a (Set.toList mb) rr) r $
104+ Map.toList m0
105+ in (m0, BiMultiMap m1 r1)
106+
107+-- | Like 'partitionWithKeyBy1st' but the predicates takes the second component
108+-- of the triplets as first argument.
109+partitionWithKeyBy2nd :: (Ord a, Ord b, Ord v)
110+ => (b -> Set (a, v) -> Bool) -> BiMultiMap a b v
111+ -> (Map b (Set (a, v)), BiMultiMap a b v)
112+partitionWithKeyBy2nd p b = let (m, b') = partitionWithKeyBy1st p $ flip b
113+ in (m, flip b')
114+
115+-- | Exchange the first and the second components of all triplets.
116+flip :: BiMultiMap a b v -> BiMultiMap b a v
117+flip (BiMultiMap m r) = BiMultiMap r m
118+
119+-- Internal functions
120+
121+-- | @reverseDelete a bs m@ removes from @m@ all the triplets wich have @a@ as
122+-- first component and second and third components in @bs@.
123+--
124+-- The @m@ map is in reversed form, meaning that the second component of the
125+-- triplets is used as key.
126+reverseDelete :: (Ord a, Ord b, Ord v)
127+ => a -> [(b, v)] -> Map b (Set (a, v)) -> Map b (Set (a, v))
128+reverseDelete a bs r = foldl' (\rr (b, v) -> Map.update (rmb v) b rr) r bs
129+ where
130+ rmb v = nothingWhen Set.null . Set.delete (a, v)
131+
132+-- | @nothingWhen p a@ is @Just a@ when @a@ satisfies predicate @p@.
133+-- Yields @Nothing@ otherwise.
134+nothingWhen :: (a -> Bool) -> a -> Maybe a
135+nothingWhen p a = if p a then Nothing else Just a
src/Control/Distributed/Process/Internal/CQueue.hs+66−15
@@ -1,3 +1,5 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveFunctor #-}
13{-# LANGUAGE BangPatterns #-}
24{-# LANGUAGE MagicHash, UnboxedTuples, PatternGuards, ScopedTypeVariables, RankNTypes #-}
35-- | Concurrent queue for single reader, single writer
@@ -10,6 +12,7 @@ module Control.Distributed.Process.Internal.CQueue
1012 , enqueueSTM
1113 , dequeue
1214 , mkWeakCQueue
15+ , queueSize
1316 ) where
1417
1518import Prelude hiding (length, reverse)
@@ -17,10 +20,14 @@ import Control.Concurrent.STM
1720 ( atomically
1821 , STM
1922 , TChan
23+ , TVar
24+ , modifyTVar'
2025 , tryReadTChan
2126 , newTChan
27+ , newTVarIO
2228 , writeTChan
2329 , readTChan
30+ , readTVarIO
2431 , orElse
2532 , retry
2633 )
@@ -38,39 +45,56 @@ import Control.Distributed.Process.Internal.StrictList
3845 , append
3946 )
4047import Data.Maybe (fromJust)
48+import Data.Traversable (traverse)
4149import GHC.MVar (MVar(MVar))
42−import GHC.IO (IO(IO))
43−import GHC.Prim (mkWeak#)
50+import GHC.IO (IO(IO), unIO)
51+import GHC.Exts (mkWeak#)
4452import GHC.Weak (Weak(Weak))
4553
4654-- We use a TCHan rather than a Chan so that we have a non-blocking read
4755data CQueue a = CQueue (StrictMVar (StrictList a)) -- Arrived
4856 (TChan a) -- Incoming
57+ (TVar Int) -- Queue size
4958
5059newCQueue :: IO (CQueue a)
51−newCQueue = CQueue <$> newMVar Nil <*> atomically newTChan
60+newCQueue = CQueue <$> newMVar Nil <*> atomically newTChan <*> newTVarIO 0
5261
5362-- | Enqueue an element
5463--
5564-- Enqueue is strict.
5665enqueue :: CQueue a -> a -> IO ()
57−enqueue (CQueue _arrived incoming) !a = atomically $ writeTChan incoming a
66+enqueue c !a = atomically (enqueueSTM c a)
5867
5968-- | Variant of enqueue for use in the STM monad.
6069enqueueSTM :: CQueue a -> a -> STM ()
61−enqueueSTM (CQueue _arrived incoming) !a = writeTChan incoming a
70+enqueueSTM (CQueue _arrived incoming size) !a = do
71+ writeTChan incoming a
72+ modifyTVar' size succ
6273
6374data BlockSpec =
6475 NonBlocking
6576 | Blocking
6677 | Timeout Int
6778
79+-- Match operations
80+--
81+-- They can be either a message match or a channel match.
6882data MatchOn m a
6983 = MatchMsg (m -> Maybe a)
7084 | MatchChan (STM a)
85+ deriving (Functor)
7186
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.
7292type MatchChunks m a = [Either [m -> Maybe a] [STM a]]
7393
94+-- Splits a list of matches into chunks.
95+--
96+-- > concatMap (either (map MatchMsg) (map MatchChan)) . chunkMatches == id
97+--
7498chunkMatches :: [MatchOn m a] -> MatchChunks m a
7599chunkMatches [] = []
76100chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest
@@ -78,6 +102,7 @@ chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest
78102chunkMatches (MatchChan r : ms) = Right (r : chk) : chunkMatches rest
79103 where (chk, rest) = spanMatchChan ms
80104
105+-- | @spanMatchMsg = first (map (\(MatchMsg x) -> x)) . span isMatchMsg@
81106spanMatchMsg :: [MatchOn m a] -> ([m -> Maybe a], [MatchOn m a])
82107spanMatchMsg [] = ([],[])
83108spanMatchMsg (m : ms)
@@ -85,6 +110,7 @@ spanMatchMsg (m : ms)
85110 | otherwise = ([], m:ms)
86111 where !(msgs,rest) = spanMatchMsg ms
87112
113+-- | @spanMatchMsg = first (map (\(MatchChan x) -> x)) . span isMatchChan@
88114spanMatchChan :: [MatchOn m a] -> ([STM a], [MatchOn m a])
89115spanMatchChan [] = ([],[])
90116spanMatchChan (m : ms)
@@ -101,7 +127,7 @@ dequeue :: forall m a.
101127 -> BlockSpec -- ^ Blocking behaviour
102128 -> [MatchOn m a] -- ^ List of matches
103129 -> IO (Maybe a) -- ^ 'Nothing' only on timeout
104−dequeue (CQueue arrived incoming) blockSpec matchons =
130+dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJust $
105131 case blockSpec of
106132 Timeout n -> timeout n $ fmap fromJust run
107133 _other ->
@@ -113,9 +139,17 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
113139 -- no onException needed
114140 _other -> run
115141 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+
116150 chunks = chunkMatches matchons
117151
118− run = mask_ $ do
152+ run = do
119153 arr <- takeMVar arrived
120154 let grabNew xs = do
121155 r <- atomically $ tryReadTChan incoming
@@ -125,17 +159,25 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
125159 arr' <- grabNew arr
126160 goCheck chunks arr'
127161
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))
128166 waitChans ports on_block =
129− foldr orElse on_block (map (fmap Just) ports)
167+ foldr orElse on_block (map (fmap (Just . Left)) ports)
130168
131169 --
132170 -- First check the MatchChunks against the messages already in the
133171 -- mailbox. For channel matches, we do a non-blocking check at
134172 -- this point.
135173 --
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+ --
136178 goCheck :: MatchChunks m a
137179 -> StrictList m -- messages to check, in this order
138− -> IO (Maybe a)
180+ -> IO (Maybe (Either a a))
139181
140182 goCheck [] old = goWait old
141183
@@ -152,7 +194,7 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
152194 -- of passing around restore and setting up exception handlers is
153195 -- high. So just don't use expensive matchIfs!
154196 case checkArrived matches old of
155− (old', Just r) -> returnOld old' (Just r)
197+ (old', Just r) -> returnOld old' (Just (Right r))
156198 (old', Nothing) -> goCheck rest old'
157199 -- use the result list, which is now left-biased
158200
@@ -181,6 +223,7 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
181223 -- Contents of 'arrived' from now on is (old ++ new), and
182224 -- messages that arrive are snocced onto new.
183225 --
226+ goWait :: StrictList m -> IO (Maybe (Either a a))
184227 goWait old = do
185228 r <- waitIncoming `onException` putMVar arrived old
186229 case r of
@@ -198,7 +241,7 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
198241 --
199242 -- Right => message arrived on a channel first
200243 --
201− Right a -> returnOld old (Just a)
244+ Right a -> returnOld old (Just (Left a))
202245
203246 --
204247 -- A message arrived in the process inbox; check the MatchChunks for
@@ -207,7 +250,7 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
207250 goCheck1 :: MatchChunks m a
208251 -> m -- single message to check
209252 -> StrictList m -- old messages we have already checked
210− -> IO (Maybe a)
253+ -> IO (Maybe (Either a a))
211254
212255 goCheck1 [] m old = goWait (Snoc old m)
213256
@@ -220,16 +263,17 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
220263 goCheck1 (Left matches : rest) m old = do
221264 case checkMatches matches m of
222265 Nothing -> goCheck1 rest m old
223− Just p -> returnOld old (Just p)
266+ Just p -> returnOld old (Just (Right p))
224267
225268 -- a common pattern for putting back the arrived queue at the end
226− returnOld :: StrictList m -> Maybe a -> IO (Maybe a)
269+ returnOld :: StrictList m -> Maybe (Either a a) -> IO (Maybe (Either a a))
227270 returnOld old r = do putMVar arrived old; return r
228271
229272 -- as a side-effect, this left-biases the list
230273 checkArrived :: [m -> Maybe a] -> StrictList m -> (StrictList m, Maybe a)
231274 checkArrived matches list = go list Nil
232275 where
276+ -- @go xs ys@ searches for a message match in @append xs ys@
233277 go Nil Nil = (Nil, Nothing)
234278 go Nil r = go r Nil
235279 go (Append xs ys) tl = go xs (append ys tl)
@@ -245,5 +289,12 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
245289
246290-- | Weak reference to a CQueue
247291mkWeakCQueue :: CQueue a -> IO () -> IO (Weak (CQueue a))
248−mkWeakCQueue m@(CQueue (StrictMVar (MVar m#)) _) f = IO $ \s ->
292+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
249296 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+6−5
@@ -28,7 +28,7 @@ module Control.Distributed.Process.Internal.Closure.BuiltIn
2828 , cpExpect
2929 , cpNewChan
3030 -- * Support for some CH operations
31− , cpDelay
31+ , cpDelayed
3232 , cpEnableTraceRemote
3333 ) where
3434
@@ -295,19 +295,20 @@ cpEnableTraceRemote =
295295-- | @delay them p@ is a process that waits for a signal (a message of type @()@)
296296-- from 'them' (origin is not verified) before proceeding as @p@. In order to
297297-- avoid waiting forever, @delay them p@ monitors 'them'. If it receives a
298−-- monitor message instead it simply terminates.
298+-- monitor message instead, it proceeds as @p@ too.
299299delay :: ProcessId -> Process () -> Process ()
300300delay them p = do
301301 ref <- monitor them
302302 let sameRef (ProcessMonitorNotification ref' _ _) = ref == ref'
303303 receiveWait [
304− match $ \() -> unmonitor ref >> p
304+ match $ \() -> unmonitor ref
305305 , matchIf sameRef $ \_ -> return ()
306306 ]
307+ p
307308
308309-- | 'CP' version of 'delay'
309−cpDelay :: ProcessId -> Closure (Process ()) -> Closure (Process ())
310−cpDelay = closureApply . cpDelay'
310+cpDelayed :: ProcessId -> Closure (Process ()) -> Closure (Process ())
311+cpDelayed = closureApply . cpDelay'
311312 where
312313 cpDelay' :: ProcessId -> Closure (Process () -> Process ())
313314 cpDelay' pid = closure decoder (encode pid)
src/Control/Distributed/Process/Internal/Closure/Explicit.hs+1−2
@@ -6,7 +6,6 @@
66 , UndecidableInstances
77 , KindSignatures
88 , GADTs
9− , OverlappingInstances
109 , EmptyDataDecls
1110 , DeriveDataTypeable #-}
1211module Control.Distributed.Process.Internal.Closure.Explicit
@@ -109,7 +108,7 @@ instance Binary EndOfTuple where
109108class Curry a b | a -> b where
110109 curryFun :: a -> b
111110
112−instance Curry ((a,EndOfTuple) -> b) (a -> b) where
111+instance Curry ((a, EndOfTuple) -> b) (a -> b) where
113112 curryFun f = \x -> f (x,undefined)
114113
115114instance Curry (b -> c) r => Curry ((a,b) -> c) (a -> r) where
src/Control/Distributed/Process/Internal/Closure/TH.hs+24−8
@@ -1,5 +1,5 @@
11-- | Template Haskell support
2−{-# LANGUAGE TemplateHaskell #-}
2+{-# LANGUAGE TemplateHaskell, CPP #-}
33module Control.Distributed.Process.Internal.Closure.TH
44 ( -- * User-level API
55 remotable
@@ -29,7 +29,14 @@ import Language.Haskell.TH
2929 , Type(AppT, ForallT, VarT, ArrowT)
3030 , Info(VarI)
3131 , TyVarBndr(PlainTV, KindedTV)
32− , Pred(ClassP)
32+ , Pred
33+#if MIN_VERSION_template_haskell(2,10,0)
34+ , conT
35+ , appT
36+#else
37+ , classP
38+#endif
39+ , varT
3340 -- Lifted constructors
3441 -- .. Literals
3542 , stringL
@@ -244,16 +251,21 @@ generateStatic :: Name -> [TyVarBndr] -> Type -> Q [Dec]
244251generateStatic n xs typ = do
245252 staticTyp <- [t| Static |]
246253 sequence
247− [ sigD (staticName n) $
254+ [ sigD (staticName n) $ do
255+ txs <- sequence $ map typeable xs
248256 return (ForallT xs
249− (map typeable xs)
250− (staticTyp `AppT` typ)
251− )
257+ txs
258+ (staticTyp `AppT` typ))
252259 , sfnD (staticName n) [| staticLabel $(showFQN n) |]
253260 ]
254261 where
255− typeable :: TyVarBndr -> Pred
256− typeable tv = ClassP (mkName "Typeable") [VarT (tyVarBndrName tv)]
262+ typeable :: TyVarBndr -> Q 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)]
268+#endif
257269
258270-- | Generate a serialization dictionary with name 'n' for type 'typ'
259271generateDict :: Name -> Type -> Q [Dec]
@@ -291,7 +303,11 @@ getType :: Name -> Q (Name, Type)
291303getType name = do
292304 info <- reify name
293305 case info of
306+#if MIN_VERSION_template_haskell(2,11,0)
307+ VarI origName typ _ -> return (origName, typ)
308+#else
294309 VarI origName typ _ _ -> return (origName, typ)
310+#endif
295311 _ -> fail $ show name ++ " not found"
296312
297313-- | Variation on 'funD' which takes a single expression to define the function
src/Control/Distributed/Process/Internal/Messaging.hs+36−26
@@ -13,10 +13,11 @@ import Data.Binary (Binary, encode)
1313import qualified Data.Map as Map (partitionWithKey, elems)
1414import qualified Data.ByteString.Lazy as BSL (toChunks)
1515import qualified Data.ByteString as BSS (ByteString)
16−import Control.Distributed.Process.Internal.StrictMVar (withMVar, modifyMVar_)
1716import Control.Distributed.Process.Serializable ()
1817
18+import Control.Concurrent (forkIO)
1919import Control.Concurrent.Chan (writeChan)
20+import Control.Exception (mask_)
2021import Control.Monad (unless)
2122import Control.Monad.IO.Class (liftIO)
2223import Control.Monad.Reader (ask)
@@ -29,7 +30,10 @@ import qualified Network.Transport as NT
2930 , close
3031 )
3132import Control.Distributed.Process.Internal.Types
32− ( LocalNode(localState, localEndPoint, localCtrlChan)
33+ ( LocalNode(localEndPoint, localCtrlChan)
34+ , withValidLocalState
35+ , modifyValidLocalState
36+ , modifyValidLocalState_
3337 , Identifier
3438 , localConnections
3539 , localConnectionBetween
@@ -50,6 +54,7 @@ import Control.Distributed.Process.Internal.Types
5054 , Identifier(NodeIdentifier, ProcessIdentifier, SendPortIdentifier)
5155 )
5256import Control.Distributed.Process.Serializable (Serializable)
57+import Data.Foldable (forM_)
5358
5459--------------------------------------------------------------------------------
5560-- Message sending --
@@ -73,7 +78,7 @@ sendPayload node from to implicitReconnect payload = do
7378 unless didSend $ do
7479 writeChan (localCtrlChan node) NCMsg
7580 { ctrlMsgSender = to
76− , ctrlMsgSignal = Died to DiedDisconnect
81+ , ctrlMsgSignal = Died (NodeIdentifier $ nodeOf to) DiedDisconnect
7782 }
7883
7984sendBinary :: Binary a
@@ -102,7 +107,7 @@ setupConnBetween :: LocalNode
102107 -> ImplicitReconnect
103108 -> IO (Maybe NT.Connection)
104109setupConnBetween node from to implicitReconnect = do
105− mConn <- NT.connect endPoint
110+ mConn <- NT.connect (localEndPoint node)
106111 (nodeAddress . nodeOf $ to)
107112 NT.ReliableOrdered
108113 NT.defaultConnectHints
@@ -113,14 +118,11 @@ setupConnBetween node from to implicitReconnect = do
113118 Left _ ->
114119 return Nothing
115120 Right () -> do
116− modifyMVar_ nodeState $ return .
117− (localConnectionBetween from to ^= Just (conn, implicitReconnect))
121+ modifyValidLocalState_ node $
122+ return . (localConnectionBetween from to ^= Just (conn, implicitReconnect))
118123 return $ Just conn
119124 Left _ ->
120125 return Nothing
121− where
122− endPoint = localEndPoint node
123− nodeState = localState node
124126
125127connBetween :: LocalNode
126128 -> Identifier
@@ -128,33 +130,37 @@ connBetween :: LocalNode
128130 -> ImplicitReconnect
129131 -> IO (Maybe NT.Connection)
130132connBetween node from to implicitReconnect = do
131− mConn <- withMVar nodeState $ return . (^. localConnectionBetween from to)
133+ mConn <- withValidLocalState node $
134+ return . (^. localConnectionBetween from to)
132135 case mConn of
133136 Just (conn, _) ->
134137 return $ Just conn
135138 Nothing ->
136139 setupConnBetween node from to implicitReconnect
137− where
138− nodeState = localState node
139140
140141disconnect :: LocalNode -> Identifier -> Identifier -> IO ()
141−disconnect node from to =
142− modifyMVar_ (localState node) $ \st ->
143− case st ^. localConnectionBetween from to of
142+disconnect node from to = mask_ $ do
143+ mio <- modifyValidLocalState node $ \vst ->
144+ case vst ^. localConnectionBetween from to of
144145 Nothing ->
145− return st
146+ return (vst, return ())
146147 Just (conn, _) -> do
147− NT.close conn
148− return (localConnectionBetween from to ^= Nothing $ st)
148+ return ( localConnectionBetween from to ^= Nothing $ vst
149+ , NT.close conn
150+ )
151+ forM_ mio forkIO
149152
150153closeImplicitReconnections :: LocalNode -> Identifier -> IO ()
151−closeImplicitReconnections node to =
152− modifyMVar_ (localState node) $ \st -> do
154+closeImplicitReconnections node to = mask_ $ do
155+ mconns <- modifyValidLocalState node $ \vst -> do
153156 let shouldClose (_, to') (_, WithImplicitReconnect) = to `impliesDeathOf` to'
154157 shouldClose _ _ = False
155− let (affected, unaffected) = Map.partitionWithKey shouldClose (st ^. localConnections)
156− mapM_ (NT.close . fst) (Map.elems affected)
157− return (localConnections ^= unaffected $ st)
158+ let (affected, unaffected) =
159+ Map.partitionWithKey shouldClose (vst ^. localConnections)
160+ return ( localConnections ^= unaffected $ vst
161+ , map fst $ Map.elems affected
162+ )
163+ forM_ mconns $ forkIO . mapM_ NT.close
158164
159165-- | @a `impliesDeathOf` b@ is true if the death of @a@ (for instance, a node)
160166-- implies the death of @b@ (for instance, a process on that node)
@@ -177,7 +183,11 @@ _ `impliesDeathOf` _ =
177183 False
178184
179185
180−-- Send a control message
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.
181191sendCtrlMsg :: Maybe NodeId -- ^ Nothing for the local node
182192 -> ProcessSignal -- ^ Message to send
183193 -> Process ()
@@ -188,10 +198,10 @@ sendCtrlMsg mNid signal = do
188198 }
189199 case mNid of
190200 Nothing -> do
191− liftIO $ writeChan (localCtrlChan (processNode proc)) msg
201+ liftIO $ writeChan (localCtrlChan (processNode proc)) $! msg
192202 Just nid ->
193203 liftIO $ sendBinary (processNode proc)
194− (ProcessIdentifier (processId proc))
204+ (NodeIdentifier (processNodeId $ processId proc))
195205 (NodeIdentifier nid)
196206 WithImplicitReconnect
197207 msg
src/Control/Distributed/Process/Internal/Primitives.hs+241−105
@@ -1,6 +1,7 @@
11{-# LANGUAGE CPP #-}
22{-# LANGUAGE RankNTypes #-}
33{-# LANGUAGE DeriveDataTypeable #-}
4+{-# LANGUAGE DeriveFunctor #-}
45{-# LANGUAGE ScopedTypeVariables #-}
56{-# LANGUAGE ExistentialQuantification #-}
67{-# LANGUAGE BangPatterns #-}
@@ -12,6 +13,7 @@
1213module Control.Distributed.Process.Internal.Primitives
1314 ( -- * Basic messaging
1415 send
16+ , usend
1517 , expect
1618 -- * Channels
1719 , newChan
@@ -21,8 +23,10 @@ module Control.Distributed.Process.Internal.Primitives
2123 , mergePortsRR
2224 -- * Unsafe messaging variants
2325 , unsafeSend
26+ , unsafeUSend
2427 , unsafeSendChan
2528 , unsafeNSend
29+ , unsafeNSendRemote
2630 -- * Advanced messaging
2731 , Match
2832 , receiveWait
@@ -45,6 +49,7 @@ module Control.Distributed.Process.Internal.Primitives
4549 , handleMessage_
4650 , handleMessageIf_
4751 , forward
52+ , uforward
4853 , delegate
4954 , relay
5055 , proxy
@@ -71,8 +76,11 @@ module Control.Distributed.Process.Internal.Primitives
7176 , unlink
7277 , monitor
7378 , unmonitor
79+ , unmonitorAsync
7480 , withMonitor
81+ , withMonitor_
7582 -- * Logging
83+ , SayMessage(..)
7684 , say
7785 -- * Registry
7886 , register
@@ -120,17 +128,27 @@ module Control.Distributed.Process.Internal.Primitives
120128import Prelude hiding (catch)
121129#endif
122130
123−import Data.Binary (decode)
124−import Data.Time.Clock (getCurrentTime)
131+import Data.Binary (Binary(..), Put, Get, decode)
132+import Data.Time.Clock (getCurrentTime, UTCTime(..))
133+import Data.Time.Calendar (Day(..))
125134import Data.Time.Format (formatTime)
135+#if MIN_VERSION_time(1,5,0)
136+import Data.Time.Format (defaultTimeLocale)
137+#else
126138import System.Locale (defaultTimeLocale)
139+#endif
127140import System.Timeout (timeout)
128−import Control.Monad (when)
141+import Control.Monad (when, void)
129142import Control.Monad.Reader (ask)
130143import Control.Monad.IO.Class (liftIO)
131−import Control.Applicative ((<$>))
132−import Control.Exception (Exception(..), throw, throwIO, SomeException)
133−import qualified Control.Exception as Ex (catch, mask, mask_, try)
144+import Control.Monad.Catch
145+ ( Exception
146+ , SomeException
147+ , throwM
148+ , fromException
149+ )
150+import qualified Control.Monad.Catch as Catch
151+import Control.Applicative
134152import Control.Distributed.Process.Internal.StrictMVar
135153 ( StrictMVar
136154 , modifyMVar
@@ -170,6 +188,7 @@ import Control.Distributed.Process.Internal.Types
170188 , SpawnRef(..)
171189 , ProcessSignal(..)
172190 , NodeMonitorNotification(..)
191+ , ProcessMonitorNotification(..)
173192 , monitorCounter
174193 , spawnCounter
175194 , SendPort(..)
@@ -194,7 +213,6 @@ import Control.Distributed.Process.Internal.Types
194213 , isEncoded
195214 , createMessage
196215 , createUnencodedMessage
197− , runLocalProcess
198216 , ImplicitReconnect( NoImplicitReconnect)
199217 , LocalProcessState
200218 , LocalSendPortId
@@ -218,6 +236,7 @@ import Control.Distributed.Process.Internal.WeakTQueue
218236 , readTQueue
219237 , mkWeakTQueue
220238 )
239+import Prelude
221240
222241import Unsafe.Coerce
223242
@@ -235,17 +254,15 @@ send them msg = do
235254 node = processNode proc
236255 nodeId = localNodeId node
237256 destNode = (processNodeId them) in do
238− case destNode == nodeId of
239− True -> sendLocal them msg
240− False -> liftIO $ sendMessage (processNode proc)
241− (ProcessIdentifier (processId proc))
242− (ProcessIdentifier them)
243− NoImplicitReconnect
244− msg
245− -- We do not fire the trace event until after the sending is complete;
246− -- In the remote case, 'sendMessage' can block in the networking stack.
247257 liftIO $ traceEvent (localEventBus node)
248258 (MxSent them us (createUnencodedMessage msg))
259+ if destNode == nodeId
260+ then sendLocal them msg
261+ else liftIO $ sendMessage (processNode proc)
262+ (ProcessIdentifier (processId proc))
263+ (ProcessIdentifier them)
264+ NoImplicitReconnect
265+ msg
249266
250267-- | /Unsafe/ variant of 'send'. This function makes /no/ attempt to serialize
251268-- and (in the case when the destination process resides on the same local
@@ -254,6 +271,34 @@ send them msg = do
254271unsafeSend :: Serializable a => ProcessId -> a -> Process ()
255272unsafeSend = Unsafe.send
256273
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+
257302-- | Wait for a message of a specific type
258303expect :: forall a. Serializable a => Process a
259304expect = receiveWait [match return]
@@ -262,7 +307,13 @@ expect = receiveWait [match return]
262307-- Channels --
263308--------------------------------------------------------------------------------
264309
265−-- | Create a new typed channel
310+-- | Create a new typed channel, bound to the calling Process
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+--
266317newChan :: Serializable a => Process (SendPort a, ReceivePort a)
267318newChan = do
268319 proc <- ask
@@ -290,13 +341,16 @@ newChan = do
290341sendChan :: Serializable a => SendPort a -> a -> Process ()
291342sendChan (SendPort cid) msg = do
292343 proc <- ask
293− let node = localNodeId (processNode proc)
294− destNode = processNodeId (sendPortProcessId cid) in do
295− case destNode == node of
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
296350 True -> sendChanLocal cid msg
297351 False -> do
298− liftIO $ sendBinary (processNode proc)
299− (ProcessIdentifier (processId proc))
352+ liftIO $ sendBinary node
353+ (ProcessIdentifier pid)
300354 (SendPortIdentifier cid)
301355 NoImplicitReconnect
302356 msg
@@ -351,13 +405,17 @@ mergePortsRR = \ps -> do
351405
352406-- | Opaque type used in 'receiveWait' and 'receiveTimeout'
353407newtype Match b = Match { unMatch :: MatchOn Message (Process b) }
408+ deriving (Functor)
354409
355410-- | Test the matches in order against each message in the queue
356411receiveWait :: [Match b] -> Process b
357412receiveWait ms = do
358413 queue <- processQueue <$> ask
359− Just proc <- liftIO $ dequeue queue Blocking (map unMatch ms)
360− proc
414+ mProc <- liftIO $ dequeue queue Blocking (map unMatch ms)
415+ case mProc of
416+ Just proc' -> proc'
417+ Nothing -> die $ "System Invariant Violation: CQueue.hs returned `Nothing` "
418+ ++ "in the absence of a timeout value."
361419
362420-- | Like 'receiveWait' but with a timeout.
363421--
@@ -436,18 +494,31 @@ forward msg them = do
436494 us = processId proc
437495 nid = localNodeId node
438496 destNode = (processNodeId them) in do
439− case destNode == nid of
440− True -> sendCtrlMsg Nothing (LocalSend them msg)
441− False -> liftIO $ sendPayload (processNode proc)
442− (ProcessIdentifier (processId proc))
443− (ProcessIdentifier them)
444− NoImplicitReconnect
445− (messageToPayload msg)
446− -- We do not fire the trace event until after the sending is complete;
447− -- In the remote case, 'sendMessage' can block in the networking stack.
448− liftIO $ traceEvent (localEventBus node)
449− (MxSent them us msg)
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)
450505
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
451522
452523-- | Wrap a 'Serializable' value in a 'Message'. Note that 'Message's are
453524-- 'Serializable' - like the datum they contain - but also note, deserialising
@@ -644,7 +715,7 @@ instance Exception ProcessTerminationException
644715
645716-- | Terminate immediately (throws a ProcessTerminationException)
646717terminate :: Process a
647−terminate = liftIO $ throwIO ProcessTerminationException
718+terminate = throwM ProcessTerminationException
648719
649720-- [Issue #110]
650721-- | Die immediately - throws a 'ProcessExitException' with the given @reason@.
@@ -654,7 +725,7 @@ die reason = do
654725-- could be decoded by a handler passed to 'catchExit', re-thrown or even
655726-- passed to another process via the tracing mechanism.
656727 pid <- getSelfPid
657− liftIO $ throwIO (ProcessExitException pid (createMessage reason))
728+ throwM (ProcessExitException pid (createMessage reason))
658729
659730-- | Forceful request to kill a process. Where 'exit' provides an exception
660731-- that can be caught and handled, 'kill' throws an unexposed exception type
@@ -687,12 +758,12 @@ catchExit :: forall a b . (Show a, Serializable a)
687758 => Process b
688759 -> (ProcessId -> a -> Process b)
689760 -> Process b
690−catchExit act exitHandler = catch act handleExit
761+catchExit act exitHandler = Catch.catch act handleExit
691762 where
692763 handleExit ex@(ProcessExitException from msg) =
693764 if messageFingerprint msg == fingerprint (undefined :: a)
694765 then exitHandler from decoded
695− else liftIO $ throwIO ex
766+ else throwM ex
696767 where
697768 decoded :: a
698769 -- Make sure the value is fully decoded so that we don't hang to
@@ -711,12 +782,12 @@ catchExit act exitHandler = catch act handleExit
711782catchesExit :: Process b
712783 -> [(ProcessId -> Message -> (Process (Maybe b)))]
713784 -> Process b
714−catchesExit act handlers = catch act ((flip handleExit) handlers)
785+catchesExit act handlers = Catch.catch act ((flip handleExit) handlers)
715786 where
716787 handleExit :: ProcessExitException
717788 -> [(ProcessId -> Message -> Process (Maybe b))]
718789 -> Process b
719− handleExit ex [] = liftIO $ throwIO ex
790+ handleExit ex [] = throwM ex
720791 handleExit ex@(ProcessExitException from msg) (h:hs) = do
721792 r <- h from msg
722793 case r of
@@ -738,11 +809,11 @@ getNodeStats nid = do
738809 selfNode <- getSelfNode
739810 if nid == selfNode
740811 then Right `fmap` getLocalNodeStats -- optimisation
741− else getNodeStatsRemote
812+ else getNodeStatsRemote selfNode
742813 where
743− getNodeStatsRemote :: Process (Either DiedReason NodeStats)
744− getNodeStatsRemote = do
745− sendCtrlMsg (Just nid) $ GetNodeStats nid
814+ getNodeStatsRemote :: NodeId -> Process (Either DiedReason NodeStats)
815+ getNodeStatsRemote selfNode = do
816+ sendCtrlMsg (Just nid) $ GetNodeStats selfNode
746817 bracket (monitorNode nid) unmonitor $ \mRef ->
747818 receiveWait [ match (\(stats :: NodeStats) -> return $ Right stats)
748819 , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef)
@@ -825,12 +896,19 @@ monitor = monitor' . ProcessIdentifier
825896-- @withMonitor@ returns, there might still be unreceived monitor
826897-- messages in the queue.
827898--
828−withMonitor :: ProcessId -> Process a -> Process a
829−withMonitor pid code = bracket (monitor pid) unmonitor (\_ -> code)
830− -- unmonitor blocks waiting for the response, so there's a possibility
831− -- that an exception might interrupt withMonitor before the unmonitor
832− -- has completed. I think that's better than making the unmonitor
833− -- uninterruptible.
899+withMonitor :: ProcessId -> (MonitorRef -> Process a) -> Process a
900+withMonitor pid = bracket (monitor pid) unmonitor
901+
902+-- | Establishes temporary monitoring of another process.
903+--
904+-- @withMonitor_ pid code@ sets up monitoring of @pid@ for the duration
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
834912
835913-- | Remove a link
836914--
@@ -868,12 +946,21 @@ unlinkPort sport = do
868946-- | Remove a monitor
869947--
870948-- This has the same synchronous/asynchronous nature as 'unlink'.
949+--
950+-- ProcessMonitorNotification messages for the given MonitorRef are removed from
951+-- the mailbox.
871952unmonitor :: MonitorRef -> Process ()
872953unmonitor ref = do
873954 unmonitorAsync ref
874955 receiveWait [ matchIf (\(DidUnmonitor ref') -> ref' == ref)
875956 (\_ -> return ())
876957 ]
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+ ]
877964
878965--------------------------------------------------------------------------------
879966-- Exception handling --
@@ -881,56 +968,43 @@ unmonitor ref = do
881968
882969-- | Lift 'Control.Exception.catch'
883970catch :: Exception e => Process a -> (e -> Process a) -> Process a
884−catch p h = do
885− lproc <- ask
886− liftIO $ Ex.catch (runLocalProcess lproc p) (runLocalProcess lproc . h)
971+catch = Catch.catch
972+{-# DEPRECATED catch "Use Control.Monad.Catch.catch instead" #-}
887973
888974-- | Lift 'Control.Exception.try'
889975try :: Exception e => Process a -> Process (Either e a)
890−try p = do
891− lproc <- ask
892− liftIO $ Ex.try (runLocalProcess lproc p)
976+try = Catch.try
977+{-# DEPRECATED try "Use Control.Monad.Catch.try instead" #-}
893978
894979-- | Lift 'Control.Exception.mask'
895980mask :: ((forall a. Process a -> Process a) -> Process b) -> Process b
896−mask p = do
897− lproc <- ask
898− liftIO $ Ex.mask $ \restore ->
899− runLocalProcess lproc (p (liftRestore restore))
900− where
901− liftRestore :: (forall a. IO a -> IO a)
902− -> (forall a. Process a -> Process a)
903− liftRestore restoreIO = \p2 -> do
904− ourLocalProc <- ask
905− liftIO $ restoreIO $ runLocalProcess ourLocalProc p2
981+mask = Catch.mask
982+{-# DEPRECATED mask "Use Control.Monad.Catch.mask_ instead" #-}
906983
907984-- | Lift 'Control.Exception.mask_'
908985mask_ :: Process a -> Process a
909−mask_ p = do
910− lproc <- ask
911− liftIO $ Ex.mask_ $ runLocalProcess lproc p
986+mask_ = Catch.mask_
987+{-# DEPRECATED mask_ "Use Control.Monad.Catch.mask_ instead" #-}
912988
913989-- | Lift 'Control.Exception.onException'
914990onException :: Process a -> Process b -> Process a
915−onException p what = p `catch` \e -> do _ <- what
916− liftIO $ throwIO (e :: SomeException)
991+onException = Catch.onException
992+{-# DEPRECATED onException "Use Control.Monad.Catch.onException instead" #-}
917993
918994-- | Lift 'Control.Exception.bracket'
919995bracket :: Process a -> (a -> Process b) -> (a -> Process c) -> Process c
920−bracket before after thing =
921− mask $ \restore -> do
922− a <- before
923− r <- restore (thing a) `onException` after a
924− _ <- after a
925− return r
996+bracket = Catch.bracket
997+{-# DEPRECATED bracket "Use Control.Monad.Catch.bracket instead" #-}
926998
927999-- | Lift 'Control.Exception.bracket_'
9281000bracket_ :: Process a -> Process b -> Process c -> Process c
929−bracket_ before after thing = bracket before (const after) (const thing)
1001+bracket_ = Catch.bracket_
1002+{-# DEPRECATED bracket_ "Use Control.Monad.Catch.bracket_ instead" #-}
9301003
9311004-- | Lift 'Control.Exception.finally'
9321005finally :: Process a -> Process b -> Process a
933−finally a sequel = bracket_ (return ()) sequel a
1006+finally = Catch.finally
1007+{-# DEPRECATED finally "Use Control.Monad.Catch.finally instead" #-}
9341008
9351009-- | You need this when using 'catches'
9361010data Handler a = forall e . Exception e => Handler (e -> Process a)
@@ -940,10 +1014,10 @@ instance Functor Handler where
9401014
9411015-- | Lift 'Control.Exception.catches'
9421016catches :: Process a -> [Handler a] -> Process a
943−catches proc handlers = proc `catch` catchesHandler handlers
1017+catches proc handlers = proc `Catch.catch` catchesHandler handlers
9441018
9451019catchesHandler :: [Handler a] -> SomeException -> Process a
946−catchesHandler handlers e = foldr tryHandler (throw e) handlers
1020+catchesHandler handlers e = foldr tryHandler (throwM e) handlers
9471021 where tryHandler (Handler handler) res
9481022 = case fromException e of
9491023 Just e' -> handler e'
@@ -963,7 +1037,10 @@ expectTimeout n = receiveTimeout n [match return]
9631037spawnAsync :: NodeId -> Closure (Process ()) -> Process SpawnRef
9641038spawnAsync nid proc = do
9651039 spawnRef <- getSpawnRef
966− sendCtrlMsg (Just nid) $ Spawn proc spawnRef
1040+ node <- getSelfNode
1041+ if nid == node
1042+ then sendCtrlMsg Nothing $ Spawn proc spawnRef
1043+ else sendCtrlMsg (Just nid) $ Spawn proc spawnRef
9671044 return spawnRef
9681045
9691046-- | Monitor a node (asynchronous)
@@ -1009,17 +1086,52 @@ unlinkPortAsync (SendPort cid) =
10091086-- Logging --
10101087--------------------------------------------------------------------------------
10111088
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+
10121124-- | Log a string
10131125--
1014−-- @say message@ sends a message (time, pid of the current process, message)
1015−-- to the process registered as 'logger'. By default, this process simply
1016−-- sends the string to 'stderr'. Individual Cloud Haskell backends might
1017−-- replace this with a different logger process, however.
1126+-- @say message@ sends a message of type 'SayMessage' with the current time and
1127+-- 'ProcessId' of the current process to the process registered as @logger@. By
1128+-- default, this process simply sends the string to @stderr@. Individual Cloud
1129+-- Haskell backends might replace this with a different logger process, however.
10181130say :: String -> Process ()
10191131say string = do
10201132 now <- liftIO getCurrentTime
10211133 us <- getSelfPid
1022− nsend "logger" (formatTime defaultTimeLocale "%c" now, us, string)
1134+ nsend "logger" (SayMessage now us string)
10231135
10241136--------------------------------------------------------------------------------
10251137-- Registry --
@@ -1042,9 +1154,10 @@ registerImpl :: Bool -> String -> ProcessId -> Process ()
10421154registerImpl force label pid = do
10431155 mynid <- getSelfNode
10441156 sendCtrlMsg Nothing (Register label mynid (Just pid) force)
1045− receiveWait [ matchIf (\(RegisterReply label' _) -> label == label')
1046− (\(RegisterReply _ ok) -> handleRegistrationReply label ok)
1047− ]
1157+ receiveWait
1158+ [ matchIf (\(RegisterReply label' _ _) -> label == label')
1159+ (\(RegisterReply _ ok owner) -> handleRegistrationReply label ok owner)
1160+ ]
10481161
10491162-- | Register a process with a remote registry (asynchronous).
10501163--
@@ -1053,8 +1166,10 @@ registerImpl force label pid = do
10531166--
10541167-- See comments in 'whereisRemoteAsync'
10551168registerRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
1056−registerRemoteAsync nid label pid =
1057− sendCtrlMsg (Just nid) (Register label nid (Just pid) False)
1169+registerRemoteAsync nid label pid = do
1170+ here <- getSelfNode
1171+ sendCtrlMsg (if nid == here then Nothing else Just nid)
1172+ (Register label nid (Just pid) False)
10581173
10591174reregisterRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
10601175reregisterRemoteAsync nid label pid =
@@ -1067,16 +1182,17 @@ unregister :: String -> Process ()
10671182unregister label = do
10681183 mynid <- getSelfNode
10691184 sendCtrlMsg Nothing (Register label mynid Nothing False)
1070− receiveWait [ matchIf (\(RegisterReply label' _) -> label == label')
1071− (\(RegisterReply _ ok) -> handleRegistrationReply label ok)
1072− ]
1185+ receiveWait
1186+ [ matchIf (\(RegisterReply label' _ _) -> label == label')
1187+ (\(RegisterReply _ ok owner) -> handleRegistrationReply label ok owner)
1188+ ]
10731189
10741190-- | Deal with the result from an attempted registration or unregistration
10751191-- by throwing an exception if necessary
1076−handleRegistrationReply :: String -> Bool -> Process ()
1077−handleRegistrationReply label ok =
1192+handleRegistrationReply :: String -> Bool -> Maybe ProcessId -> Process ()
1193+handleRegistrationReply label ok owner =
10781194 when (not ok) $
1079− liftIO $ throwIO $ ProcessRegistrationException label
1195+ throwM $ ProcessRegistrationException label owner
10801196
10811197-- | Remove a process from a remote registry (asynchronous).
10821198--
@@ -1105,13 +1221,18 @@ whereis label = do
11051221-- use 'monitorNode' and take appropriate action when you receive a
11061222-- 'NodeMonitorNotification').
11071223whereisRemoteAsync :: NodeId -> String -> Process ()
1108−whereisRemoteAsync nid label =
1109− sendCtrlMsg (Just nid) (WhereIs label)
1224+whereisRemoteAsync nid label = do
1225+ here <- getSelfNode
1226+ sendCtrlMsg (if nid == here then Nothing else Just nid) (WhereIs label)
11101227
11111228-- | Named send to a process in the local registry (asynchronous)
11121229nsend :: Serializable a => String -> a -> Process ()
1113−nsend label msg =
1114− sendCtrlMsg Nothing (NamedSend label (createUnencodedMessage msg))
1230+nsend label msg = do
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')
11151236
11161237-- | Named send to a process in the local registry (asynchronous).
11171238-- This function makes /no/ attempt to serialize and (in the case when the
@@ -1122,8 +1243,23 @@ unsafeNSend = Unsafe.nsend
11221243
11231244-- | Named send to a process in a remote registry (asynchronous)
11241245nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
1125−nsendRemote nid label msg =
1126− sendCtrlMsg (Just nid) (NamedSend label (createMessage msg))
1246+nsendRemote nid label msg = do
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))
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
11271263
11281264--------------------------------------------------------------------------------
11291265-- Closures --
src/Control/Distributed/Process/Internal/Spawn.hs+13−9
@@ -37,11 +37,13 @@ import Control.Distributed.Process.Internal.Closure.BuiltIn
3737 , cpLink
3838 , cpSend
3939 , cpNewChan
40− , cpDelay
40+ , cpDelayed
41+ , returnCP
42+ , sdictUnit
4143 )
4244import Control.Distributed.Process.Internal.Primitives
4345 ( -- Basic messaging
44− send
46+ usend
4547 , expect
4648 , receiveWait
4749 , match
@@ -65,11 +67,11 @@ spawn :: NodeId -> Closure (Process ()) -> Process ProcessId
6567spawn nid proc = do
6668 us <- getSelfPid
6769 mRef <- monitorNode nid
68− sRef <- spawnAsync nid (cpDelay us proc)
70+ sRef <- spawnAsync nid (cpDelayed us proc)
6971 receiveWait [
7072 matchIf (\(DidSpawn ref _) -> ref == sRef) $ \(DidSpawn _ pid) -> do
7173 unmonitor mRef
72− send pid ()
74+ usend pid ()
7375 return pid
7476 , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef) $ \_ ->
7577 return (nullProcessId nid)
@@ -109,12 +111,14 @@ call :: Serializable a
109111 -> Process a
110112call dict nid proc = do
111113 us <- getSelfPid
112− (pid, mRef) <- spawnMonitor nid (proc `bindCP` cpSend dict us)
113− -- We are guaranteed to receive the reply before the monitor notification
114− -- (if a reply is sent at all)
115− -- NOTE: This might not be true if we switch to unreliable delivery.
114+ (pid, mRef) <- spawnMonitor nid (proc `bindCP`
115+ cpSend dict us `seqCP`
116+ -- Delay so the process does not terminate
117+ -- before the response arrives.
118+ cpDelayed us (returnCP sdictUnit ())
119+ )
116120 mResult <- receiveWait
117− [ match (return . Right)
121+ [ match $ \a -> usend pid () >> return (Right a)
118122 , matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)
119123 (\(ProcessMonitorNotification _ _ reason) -> return (Left reason))
120124 ]
src/Control/Distributed/Process/Internal/StrictMVar.hs+8−4
@@ -36,8 +36,8 @@ import qualified Control.Concurrent.MVar as MVar
3636#endif
3737 )
3838import GHC.MVar (MVar(MVar))
39−import GHC.IO (IO(IO))
40−import GHC.Prim (mkWeak#)
39+import GHC.IO (IO(IO), unIO)
40+import GHC.Exts (mkWeak#)
4141import GHC.Weak (Weak(Weak))
4242
4343newtype StrictMVar a = StrictMVar (MVar.MVar a)
@@ -85,5 +85,9 @@ modifyMVarMasked (StrictMVar v) f =
8585 evaluateFst (x, y) = evaluate x >> return (x, y)
8686
8787mkWeakMVar :: StrictMVar a -> IO () -> IO (Weak (StrictMVar a))
88−mkWeakMVar m@(StrictMVar (MVar m#)) f = IO $ \s ->
89− case mkWeak# m# m f s of (# s1, w #) -> (# s1, Weak w #)
88+mkWeakMVar q@(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 #)
93+#endif
src/Control/Distributed/Process/Internal/Types.hs+150−28
@@ -3,6 +3,10 @@
33{-# LANGUAGE GeneralizedNewtypeDeriving #-}
44{-# LANGUAGE GADTs #-}
55{-# LANGUAGE DeriveGeneric #-}
6+{-# LANGUAGE CPP #-}
7+{-# LANGUAGE MultiParamTypeClasses #-}
8+{-# LANGUAGE TypeFamilies #-}
9+{-# LANGUAGE Rank2Types #-}
610
711-- | Types used throughout the Cloud Haskell framework
812--
@@ -20,9 +24,14 @@ module Control.Distributed.Process.Internal.Types
2024 , nullProcessId
2125 -- * Local nodes and processes
2226 , LocalNode(..)
27+ , LocalNodeState(..)
28+ , ValidLocalNodeState(..)
29+ , NodeClosedException(..)
30+ , withValidLocalState
31+ , modifyValidLocalState
32+ , modifyValidLocalState_
2333 , Tracer(..)
2434 , MxEventBus(..)
25− , LocalNodeState(..)
2635 , LocalProcess(..)
2736 , LocalProcessState(..)
2837 , Process(..)
@@ -101,15 +110,17 @@ import qualified Data.ByteString.Lazy.Internal as BSL (ByteString(..))
101110import Data.Accessor (Accessor, accessor)
102111import Control.Category ((>>>))
103112import Control.DeepSeq (NFData(..))
104−import Control.Exception (Exception)
113+import Control.Exception (Exception, throwIO)
105114import Control.Concurrent (ThreadId)
106115import Control.Concurrent.Chan (Chan)
107116import Control.Concurrent.STM (STM)
108117import Control.Concurrent.STM.TChan (TChan)
118+import Control.Monad.Catch (MonadThrow(..), MonadCatch(..), MonadMask(..))
109119import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)
110−import Control.Applicative (Applicative, Alternative, (<$>), (<*>))
120+import Control.Applicative
121+import Control.Monad.Fix (MonadFix)
111122import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
112−import Control.Monad.IO.Class (MonadIO)
123+import Control.Monad.IO.Class (MonadIO(..))
113124import Control.Distributed.Process.Serializable
114125 ( Fingerprint
115126 , Serializable
@@ -120,13 +131,19 @@ import Control.Distributed.Process.Serializable
120131 , showFingerprint
121132 )
122133import Control.Distributed.Process.Internal.CQueue (CQueue)
123−import Control.Distributed.Process.Internal.StrictMVar (StrictMVar)
134+import Control.Distributed.Process.Internal.StrictMVar
135+ ( StrictMVar
136+ , withMVar
137+ , modifyMVar
138+ , modifyMVar_
139+ )
124140import Control.Distributed.Process.Internal.WeakTQueue (TQueue)
125141import Control.Distributed.Static (RemoteTable, Closure)
126142import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC (mapMaybe)
127143
128144import Data.Hashable
129145import GHC.Generics
146+import Prelude
130147
131148--------------------------------------------------------------------------------
132149-- Node and process identifiers --
@@ -136,7 +153,7 @@ import GHC.Generics
136153newtype NodeId = NodeId { nodeAddress :: NT.EndPointAddress }
137154 deriving (Eq, Ord, Typeable, Data, Generic)
138155instance Binary NodeId where
139−instance NFData NodeId
156+instance NFData NodeId where rnf (NodeId a) = rnf a `seq` ()
140157instance Hashable NodeId where
141158instance Show NodeId where
142159 show (NodeId addr) = "nid://" ++ show addr
@@ -161,7 +178,7 @@ data ProcessId = ProcessId
161178 deriving (Eq, Ord, Typeable, Data, Generic)
162179
163180instance Binary ProcessId where
164−instance NFData ProcessId where
181+instance NFData ProcessId where rnf (ProcessId n _) = rnf n `seq` ()
165182instance Hashable ProcessId where
166183
167184instance Show ProcessId where
@@ -176,6 +193,10 @@ data Identifier =
176193 deriving (Eq, Ord, Generic)
177194
178195instance 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` ()
179200
180201instance Show Identifier where
181202 show (NodeIdentifier nid) = show nid
@@ -252,7 +273,11 @@ data ImplicitReconnect = WithImplicitReconnect | NoImplicitReconnect
252273 deriving (Eq, Show)
253274
254275-- | Local node state
255−data LocalNodeState = LocalNodeState
276+data LocalNodeState =
277+ LocalNodeValid {-# UNPACK #-} !ValidLocalNodeState
278+ | LocalNodeClosed
279+
280+data ValidLocalNodeState = ValidLocalNodeState
256281 { -- | Processes running on this node
257282 _localProcesses :: !(Map LocalProcessId LocalProcess)
258283 -- | Counter to assign PIDs
@@ -265,6 +290,40 @@ data LocalNodeState = LocalNodeState
265290 (NT.Connection, ImplicitReconnect))
266291 }
267292
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+
268327-- | Processes running on our local node
269328data LocalProcess = LocalProcess
270329 { processQueue :: !(CQueue Message)
@@ -291,7 +350,50 @@ data LocalProcessState = LocalProcessState
291350newtype Process a = Process {
292351 unProcess :: ReaderT LocalProcess IO a
293352 }
294− deriving (Functor, Monad, MonadIO, MonadReader LocalProcess, Typeable, Applicative)
353+ deriving ( Applicative
354+ , Functor
355+ , Monad
356+ , MonadFix
357+ , MonadIO
358+ , MonadReader LocalProcess
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
295397
296398--------------------------------------------------------------------------------
297399-- Typed channels --
@@ -316,6 +418,9 @@ instance Show SendPortId where
316418 show (SendPortId (ProcessId (NodeId addr) (LocalProcessId _ plid)) clid)
317419 = "cid://" ++ show addr ++ ":" ++ show plid ++ ":" ++ show clid
318420
421+instance NFData SendPortId where
422+ rnf (SendPortId p _) = rnf p `seq` ()
423+
319424data TypedChannel = forall a. Serializable a => TypedChannel (Weak (TQueue a))
320425
321426-- | The send send of a typed channel (serializable)
@@ -327,7 +432,7 @@ newtype SendPort a = SendPort {
327432
328433instance (Serializable a) => Binary (SendPort a) where
329434instance (Hashable a) => Hashable (SendPort a) where
330−instance (NFData a) => NFData (SendPort a) where
435+instance (NFData a) => NFData (SendPort a) where rnf (SendPort x) = x `seq` ()
331436
332437-- | The receive end of a typed channel (not serializable)
333438--
@@ -365,6 +470,15 @@ data Message =
365470 }
366471 deriving (Typeable)
367472
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+
368482instance Show Message where
369483 show (EncodedMessage fp enc) = show enc ++ " :: " ++ showFingerprint fp []
370484 show (UnencodedMessage _ uenc) = "[unencoded message] :: " ++ (show $ typeOf uenc)
@@ -428,7 +542,10 @@ data MonitorRef = MonitorRef
428542 , monitorRefCounter :: !Int32
429543 }
430544 deriving (Eq, Ord, Show, Typeable, Generic)
431−instance Hashable MonitorRef where
545+instance Hashable MonitorRef
546+
547+instance NFData MonitorRef where
548+ rnf (MonitorRef i _) = rnf i `seq` ()
432549
433550-- | Message sent by process monitors
434551data ProcessMonitorNotification =
@@ -462,9 +579,11 @@ data PortLinkException =
462579
463580-- | Exception thrown when a process attempts to register
464581-- a process under an already-registered name or to
465−-- unregister a name that hasn't been registered
582+-- unregister a name that hasn't been registered. Returns
583+-- the name and the identifier of the process that owns it,
584+-- if any.
466585data ProcessRegistrationException =
467− ProcessRegistrationException !String
586+ ProcessRegistrationException !String !(Maybe ProcessId)
468587 deriving (Typeable, Show)
469588
470589-- | Internal exception thrown indirectly by 'exit'
@@ -496,6 +615,10 @@ data DiedReason =
496615 | DiedUnknownId
497616 deriving (Show, Eq)
498617
618+instance NFData DiedReason where
619+ rnf (DiedException s) = rnf s `seq` ()
620+ rnf x = x `seq` ()
621+
499622-- | (Asynchronous) reply from unmonitor
500623newtype DidUnmonitor = DidUnmonitor MonitorRef
501624 deriving (Typeable, Binary)
@@ -514,7 +637,7 @@ newtype DidUnlinkPort = DidUnlinkPort SendPortId
514637
515638-- | 'SpawnRef' are used to return pids of spawned processes
516639newtype SpawnRef = SpawnRef Int32
517− deriving (Show, Binary, Typeable, Eq)
640+ deriving (Show, Binary, Typeable, Eq, Ord)
518641
519642-- | (Asynchronius) reply from 'spawn'
520643data DidSpawn = DidSpawn SpawnRef ProcessId
@@ -525,7 +648,7 @@ data WhereIsReply = WhereIsReply String (Maybe ProcessId)
525648 deriving (Show, Typeable)
526649
527650-- | (Asynchronous) reply from 'register' and 'unregister'
528−data RegisterReply = RegisterReply String Bool
651+data RegisterReply = RegisterReply String Bool (Maybe ProcessId)
529652 deriving (Show, Typeable)
530653
531654data NodeStats = NodeStats {
@@ -541,7 +664,7 @@ data NodeStats = NodeStats {
541664data ProcessInfo = ProcessInfo {
542665 infoNode :: NodeId
543666 , infoRegisteredNames :: [String]
544− , infoMessageQueueLength :: Maybe Int
667+ , infoMessageQueueLength :: Int
545668 , infoMonitors :: [(ProcessId, MonitorRef)]
546669 , infoLinks :: [ProcessId]
547670 } deriving (Show, Eq, Typeable)
@@ -571,6 +694,7 @@ data ProcessSignal =
571694 | WhereIs !String
572695 | Register !String !NodeId !(Maybe ProcessId) !Bool -- Use 'Nothing' to unregister, use True to force reregister
573696 | NamedSend !String !Message
697+ | UnreliableSend !LocalProcessId !Message
574698 | LocalSend !ProcessId !Message
575699 | LocalPortSend !SendPortId !Message
576700 | Kill !ProcessId !String
@@ -626,6 +750,7 @@ instance Binary ProcessSignal where
626750 put (Exit pid reason) = putWord8 10 >> put pid >> put (messageToPayload reason)
627751 put (LocalSend to' msg) = putWord8 11 >> put to' >> put (messageToPayload msg)
628752 put (LocalPortSend sid msg) = putWord8 12 >> put sid >> put (messageToPayload msg)
753+ put (UnreliableSend lpid msg) = putWord8 13 >> put lpid >> put (messageToPayload msg)
629754 put (GetInfo about) = putWord8 30 >> put about
630755 put (SigShutdown) = putWord8 31
631756 put (GetNodeStats nid) = putWord8 32 >> put nid
@@ -645,6 +770,7 @@ instance Binary ProcessSignal where
645770 10 -> Exit <$> get <*> (payloadToMessage <$> get)
646771 11 -> LocalSend <$> get <*> (payloadToMessage <$> get)
647772 12 -> LocalPortSend <$> get <*> (payloadToMessage <$> get)
773+ 13 -> UnreliableSend <$> get <*> (payloadToMessage <$> get)
648774 30 -> GetInfo <$> get
649775 31 -> return SigShutdown
650776 32 -> GetNodeStats <$> get
@@ -674,9 +800,6 @@ instance Binary SendPortId where
674800 put cid = put (sendPortProcessId cid) >> put (sendPortLocalId cid)
675801 get = SendPortId <$> get <*> get
676802
677−instance NFData SendPortId where
678− rnf cid = (sendPortProcessId cid) `seq` (sendPortLocalId cid) `seq` ()
679−
680803instance Binary Identifier where
681804 put (ProcessIdentifier pid) = putWord8 0 >> put pid
682805 put (NodeIdentifier nid) = putWord8 1 >> put nid
@@ -694,8 +817,8 @@ instance Binary WhereIsReply where
694817 get = WhereIsReply <$> get <*> get
695818
696819instance Binary RegisterReply where
697− put (RegisterReply label ok) = put label >> put ok
698− get = RegisterReply <$> get <*> get
820+ put (RegisterReply label ok owner) = put label >> put ok >> put owner
821+ get = RegisterReply <$> get <*> get <*> get
699822
700823instance Binary ProcessInfo where
701824 get = ProcessInfo <$> get <*> get <*> get <*> get <*> get
@@ -721,22 +844,22 @@ instance Binary ProcessInfoNone where
721844-- Accessors --
722845--------------------------------------------------------------------------------
723846
724−localProcesses :: Accessor LocalNodeState (Map LocalProcessId LocalProcess)
847+localProcesses :: Accessor ValidLocalNodeState (Map LocalProcessId LocalProcess)
725848localProcesses = accessor _localProcesses (\procs st -> st { _localProcesses = procs })
726849
727−localPidCounter :: Accessor LocalNodeState Int32
850+localPidCounter :: Accessor ValidLocalNodeState Int32
728851localPidCounter = accessor _localPidCounter (\ctr st -> st { _localPidCounter = ctr })
729852
730−localPidUnique :: Accessor LocalNodeState Int32
853+localPidUnique :: Accessor ValidLocalNodeState Int32
731854localPidUnique = accessor _localPidUnique (\unq st -> st { _localPidUnique = unq })
732855
733−localConnections :: Accessor LocalNodeState (Map (Identifier, Identifier) (NT.Connection, ImplicitReconnect))
856+localConnections :: Accessor ValidLocalNodeState (Map (Identifier, Identifier) (NT.Connection, ImplicitReconnect))
734857localConnections = accessor _localConnections (\conns st -> st { _localConnections = conns })
735858
736−localProcessWithId :: LocalProcessId -> Accessor LocalNodeState (Maybe LocalProcess)
859+localProcessWithId :: LocalProcessId -> Accessor ValidLocalNodeState (Maybe LocalProcess)
737860localProcessWithId lpid = localProcesses >>> DAC.mapMaybe lpid
738861
739−localConnectionBetween :: Identifier -> Identifier -> Accessor LocalNodeState (Maybe (NT.Connection, ImplicitReconnect))
862+localConnectionBetween :: Identifier -> Identifier -> Accessor ValidLocalNodeState (Maybe (NT.Connection, ImplicitReconnect))
740863localConnectionBetween from' to' = localConnections >>> DAC.mapMaybe (from', to')
741864
742865monitorCounter :: Accessor LocalProcessState Int32
@@ -762,4 +885,3 @@ typedChannelWithId cid = typedChannels >>> DAC.mapMaybe cid
762885{-# INLINE forever' #-}
763886forever' :: Monad m => m a -> m b
764887forever' a = let a' = a >> a' in a'
765−
src/Control/Distributed/Process/Internal/WeakTQueue.hs+7−2
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12{-# LANGUAGE DeriveDataTypeable #-}
23{-# LANGUAGE MagicHash, UnboxedTuples #-}
34-- | Clone of Control.Concurrent.STM.TQueue with support for mkWeakTQueue
@@ -22,8 +23,8 @@ module Control.Distributed.Process.Internal.WeakTQueue (
2223import Prelude hiding (read)
2324import GHC.Conc
2425import Data.Typeable (Typeable)
25−import GHC.IO (IO(IO))
26−import GHC.Prim (mkWeak#)
26+import GHC.IO (IO(IO), unIO)
27+import GHC.Exts (mkWeak#)
2728import GHC.Weak (Weak(Weak))
2829
2930--------------------------------------------------------------------------------
@@ -99,4 +100,8 @@ isEmptyTQueue (TQueue read write) = do
99100
100101mkWeakTQueue :: TQueue a -> IO () -> IO (Weak (TQueue a))
101102mkWeakTQueue 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
102106 case mkWeak# write# q f s of (# s', w #) -> (# s', Weak w #)
107+#endif
src/Control/Distributed/Process/Management.hs+41−83
@@ -76,6 +76,40 @@
7676--
7777-- * Whether messages will be taken from the mailbox first, or the event bus.
7878--
79+-- Since the event bus uses STM broadcast channels to communicate with agents,
80+-- no message written to the bus successfully can be lost.
81+--
82+-- Agents can also receive messages via their mailboxes - these are subject to
83+-- the same guarantees as all inter-process message sending.
84+--
85+-- Messages dispatched on an STM broadcast channel (i.e., management event bus)
86+-- are guaranteed to be delivered with the same FIFO ordering guarantees that
87+-- exist between two communicating processes, such that communication from the
88+-- node controller's threads (i.e., MxEvent's) will never be re-ordered, but
89+-- messages dispatched to the event bus by other processes (including, but not
90+-- limited to agents) are only guaranteed to be ordered between one sender and
91+-- one receiver.
92+--
93+-- No guarantee exists for the ordering in which messages sent to an agent's
94+-- mailbox will be delivered, vs messages dispatched via the event bus.
95+--
96+-- Because of the above, there are no ordering guarantees for messages sent
97+-- between agents, or for processes to agents, except for those that apply to
98+-- messages sent between regular processes, since agents are
99+-- implemented as such.
100+--
101+-- The event bus is serial and single threaded. Anything that is published by
102+-- the node controller will be seen in FIFO order. There are no ordering
103+-- guarantees pertaining to entries published to the event bus by other
104+-- processes or agents.
105+--
106+-- It should not be possible to see, for example, an @MxReceived@ before the
107+-- corresponding @MxSent@ event, since the places where we issue the @MxSent@
108+-- write directly to the event bus (using STM) in the calling (green) thread,
109+-- before dispatching instructions to the node controller to perform the
110+-- necessary routing to deliver the message to a process (or registered name,
111+-- or typed channel) locally or remotely.
112+--
79113-- [Management Data API]
80114--
81115-- Both management agents and clients of the API have access to a variety of
@@ -83,28 +117,8 @@
83117-- system information. Agents maintain their own internal state privately (via a
84118-- state transformer - see 'mxGetLocal' et al), however it is possible for
85119-- agents to share additional data with each other (and the outside world)
86−-- using /data tables/.
87−--
88−-- Each agent is assigned its own data table, which acts as a shared map, where
89−-- the keys are @String@s and the values are @Serializable@ datum of whatever
90−-- type the agent or its clients stores.
91−--
92−-- Because an agent's /data table/ stores its values in raw 'Message' format,
93−-- it works effectively as an /un-typed dictionary/, into which data of varying
94−-- types can be fed and later retrieved. The upside of this is that different
95−-- keys can be mapped to various types without any additional work on the part
96−-- of the developer. The downside is that the code reading these values must
97−-- know in advance what type(s) to expect, and the API provides no additional
98−-- support for handling that.
99−--
100−-- Publishing is accomplished using the 'mxPublish' and 'mxSet' APIs, whilst
101−-- querying and deletion are handled by 'mxGet', 'mxClear', 'mxPurgeTable' and
102−-- 'mxDropTable' respectively.
103−--
104−-- When a management agent terminates, their tables are left in memory despite
105−-- termination, such that an agent may resume its role (by restarting) or have
106−-- its 'MxAgentId' taken over by another subsequent agent, leaving the data
107−-- originally captured in place.
120+-- using whatever mechanism the user wishes, e.g., acidstate, or shared memory
121+-- primitives.
108122--
109123-- [Defining Agents]
110124--
@@ -164,8 +178,6 @@
164178-- > monitorNames = getSelfPid >>= nsend "name-monitor"
165179-- > monitorNames2 = getSelfPid >>= mxNotify
166180--
167−-- For some real-world examples, see the distributed-process-platform package.
168−--
169181-- [Performance, Stablity and Scalability]
170182--
171183-- /Management Agents/ offer numerous advantages over regular processes:
@@ -194,10 +206,6 @@
194206-- the event bus /and/ their own mailboxes, plus searching through the set of
195207-- event sinks (for each agent) to determine the right handler for the event.
196208--
197−-- Each management agent requires not only its own @Process@ (in which the agent
198−-- code is run), but also a peer process that provides its /data table/. These
199−-- data tables also have to be coordinated and manaaged on each agent's behalf.
200−--
201209-- [Architecture Overview]
202210--
203211-- The architecture of the management event bus is internal and subject to
@@ -264,16 +272,9 @@ module Control.Distributed.Process.Management
264272 , mxGetLocal
265273 , mxUpdateLocal
266274 , liftMX
267− -- * Mx Data API
268− , mxPublish
269− , mxSet
270− , mxGet
271− , mxClear
272− , mxPurgeTable
273− , mxDropTable
274275 ) where
275276
276−import Control.Applicative ((<$>))
277+import Control.Applicative
277278import Control.Concurrent.STM (atomically)
278279import Control.Concurrent.STM.TChan
279280 ( readTChan
@@ -281,14 +282,10 @@ import Control.Concurrent.STM.TChan
281282 , TChan
282283 )
283284import Control.Distributed.Process.Internal.Primitives
284− ( newChan
285− , nsend
286− , receiveWait
287− , matchChan
285+ ( receiveWait
288286 , matchAny
289287 , matchSTM
290288 , unwrapMessage
291− , onException
292289 , register
293290 , whereis
294291 , die
@@ -303,26 +300,26 @@ import Control.Distributed.Process.Internal.Types
303300 , unsafeCreateUnencodedMessage
304301 )
305302import Control.Distributed.Process.Management.Internal.Bus (publishEvent)
306−import qualified Control.Distributed.Process.Management.Internal.Table as Table
307303import Control.Distributed.Process.Management.Internal.Types
308304 ( MxAgentId(..)
309305 , MxAgent(..)
310306 , MxAction(..)
311307 , ChannelSelector(..)
312308 , MxAgentState(..)
313− , MxAgentStart(..)
314309 , MxSink
315310 , MxEvent(..)
316311 )
317312import Control.Distributed.Process.Serializable (Serializable)
318313import Control.Monad.IO.Class (liftIO)
319314import Control.Monad.Reader (ask)
315+import Control.Monad.Catch (onException)
320316import qualified Control.Monad.State as ST
321317 ( get
322318 , modify
323319 , lift
324320 , runStateT
325321 )
322+import Prelude
326323
327324-- | Publishes an arbitrary @Serializable@ message to the management event bus.
328325-- Note that /no attempt is made to force the argument/, therefore it is very
@@ -334,42 +331,6 @@ mxNotify msg = do
334331 bus <- localEventBus . processNode <$> ask
335332 liftIO $ publishEvent bus $ unsafeCreateUnencodedMessage msg
336333
337−-- | Publish an arbitrary @Message@ as a property in the management database.
338−--
339−-- For publishing @Serializable@ data, use 'mxSet' instead.
340−--
341−mxPublish :: MxAgentId -> String -> Message -> Process ()
342−mxPublish a k v = Table.set k v (Table.MxForAgent a)
343−
344−-- | Sets an arbitrary @Serializable@ datum against a key in the management
345−-- database. Note that /no attempt is made to force the argument/, therefore
346−-- it is very important that you do not pass unevaluated thunks that might
347−-- crash some other, arbitrary process (or management agent!) that obtains
348−-- and attempts to force the value later on.
349−--
350−mxSet :: Serializable a => MxAgentId -> String -> a -> Process ()
351−mxSet mxId key msg = do
352− Table.set key (unsafeCreateUnencodedMessage msg) (Table.MxForAgent mxId)
353−
354−-- | Fetches a property from the management database for the given key.
355−-- If the property is not set, or does not match the expected type when
356−-- typechecked (at runtime), returns @Nothing@.
357−mxGet :: Serializable a => MxAgentId -> String -> Process (Maybe a)
358−mxGet = Table.fetch . Table.MxForAgent
359−
360−-- | Clears a property from the management database using the given key.
361−-- If the key does not exist in the database, this is a noop.
362−mxClear :: MxAgentId -> String -> Process ()
363−mxClear mxId key = Table.clear key (Table.MxForAgent mxId)
364−
365−-- | Purges a table in the management database of all its stored properties.
366−mxPurgeTable :: MxAgentId -> Process ()
367−mxPurgeTable = Table.purge . Table.MxForAgent
368−
369−-- | Deletes a table from the management database.
370−mxDropTable :: MxAgentId -> Process ()
371−mxDropTable = Table.delete . Table.MxForAgent
372−
373334--------------------------------------------------------------------------------
374335-- API for writing user defined management extensions (i.e., agents) --
375336--------------------------------------------------------------------------------
@@ -489,10 +450,7 @@ mxAgentWithFinalize mxId initState handlers dtor = do
489450 return pid
490451 where
491452 start (sendTChan, recvTChan) = do
492− (sp, rp) <- newChan
493− nsend Table.mxTableCoordinator (MxAgentStart sp mxId)
494− tablePid <- receiveWait [ matchChan rp (\(p :: ProcessId) -> return p) ]
495− let nState = MxAgentState mxId sendTChan tablePid initState
453+ let nState = MxAgentState mxId sendTChan initState
496454 runAgent dtor handlers InputChan recvTChan nState
497455
498456 runAgent :: MxAgent s ()
src/Control/Distributed/Process/Management/Internal/Agent.hs+2−1
@@ -3,7 +3,7 @@
33
44module Control.Distributed.Process.Management.Internal.Agent where
55
6−import Control.Applicative ((<$>))
6+import Control.Applicative
77import Control.Concurrent (forkIO)
88import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
99import Control.Concurrent.STM (atomically)
@@ -44,6 +44,7 @@ import Control.Monad (void)
4444import Control.Monad.IO.Class (liftIO)
4545import Control.Monad.Reader (ask)
4646import GHC.Weak (Weak, deRefWeak)
47+import Prelude
4748
4849--------------------------------------------------------------------------------
4950-- Agent Controller Implementation --
src/Control/Distributed/Process/Management/Internal/Table.hsdeleted−222
@@ -1,222 +0,0 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
2−{-# LANGUAGE ScopedTypeVariables #-}
3−{-# LANGUAGE RankNTypes #-}
4−{-# LANGUAGE DeriveGeneric #-}
5−{-# LANGUAGE RecordWildCards #-}
6−
7−module Control.Distributed.Process.Management.Internal.Table
8− ( MxTableRequest(..)
9− , MxTableId(..)
10− , mxTableCoordinator
11− , startTableCoordinator
12− , delete
13− , purge
14− , clear
15− , set
16− , get
17− , fetch
18− ) where
19−
20−import Control.Distributed.Process.Internal.Primitives
21− ( receiveWait
22− , receiveChan
23− , match
24− , matchAny
25− , matchIf
26− , matchChan
27− , send
28− , nsend
29− , sendChan
30− , getSelfPid
31− , link
32− , monitor
33− , unwrapMessage
34− , newChan
35− , withMonitor
36− )
37−import Control.Distributed.Process.Internal.Types
38− ( Process
39− , ProcessId
40− , ProcessMonitorNotification(..)
41− , SendPort
42− , ReceivePort
43− , Message
44− , unsafeCreateUnencodedMessage
45− )
46−import Control.Distributed.Process.Management.Internal.Types
47− ( MxTableId(..)
48− , MxAgentId(..)
49− , MxAgentStart(..)
50− , Fork)
51−import Control.Distributed.Process.Serializable (Serializable)
52−import Control.Monad.IO.Class (liftIO)
53−import Data.Accessor (Accessor, accessor, (^=), (^:))
54−import Data.Binary (Binary)
55−import Data.Map (Map)
56−import qualified Data.Map as Map
57−import Data.Typeable (Typeable)
58−
59−import GHC.Generics
60−
61−-- An extremely lightweight shared Map implementation, for use
62−-- by /management agents/ and their cohorts. Each agent is assigned
63−-- a table, into which any serializable @Message@ can be inserted.
64−-- Data are inserted, removed and searched for via their key, which
65−-- is a string. Tables can be purged, values can be set, fetched or
66−-- cleared/removed.
67−--
68−
69−data MxTableRequest =
70− Delete
71− | Purge
72− | Clear !String
73− | Set !String !Message
74− | Get !String !(SendPort (Maybe Message)) -- see [note: un-typed send port]
75− deriving (Typeable, Generic)
76−instance Binary MxTableRequest where
77−
78−data MxTableState = MxTableState { _name :: !String
79− , _entries :: !(Map String Message)
80− }
81−
82−type MxTables = Map MxAgentId ProcessId
83−
84−mxTableCoordinator :: String
85−mxTableCoordinator = "mx.table.coordinator"
86−
87−delete :: MxTableId -> Process ()
88−delete = sendReq Delete
89−
90−purge :: MxTableId -> Process ()
91−purge = sendReq Purge
92−
93−clear :: String -> MxTableId -> Process ()
94−clear k = sendReq (Clear k)
95−
96−set :: String -> Message -> MxTableId -> Process ()
97−set k v = sendReq (Set k v)
98−
99−fetch :: forall a. (Serializable a)
100− => MxTableId
101− -> String
102− -> Process (Maybe a)
103−fetch (MxForPid pid) key = get pid key
104−fetch mxId@(MxForAgent _) key = do
105− (sp, rp) <- newChan :: Process (SendPort (Maybe Message),
106− ReceivePort (Maybe Message))
107− sendReq (Get key sp) mxId
108− receiveChan rp >>= maybe (return Nothing)
109− (unwrapMessage :: Message -> Process (Maybe a))
110−
111−-- [note: un-typed send port]
112−-- Here, fetch uses a typed channel over a raw Message to obtain
113−-- its result, so type checking is deferred until receipt and will
114−-- be handled in the caller's thread. This is necessary because
115−-- the server portion of the code knows nothing about the types
116−-- involved, nor should it, since these tables can be used to
117−-- store arbitrary serializable data.
118−
119−get :: forall a. (Serializable a)
120− => ProcessId
121− -> String
122− -> Process (Maybe a)
123−get pid key = do
124− safeFetch pid key >>= maybe (return Nothing)
125− (unwrapMessage :: Message -> Process (Maybe a))
126−
127−safeFetch :: ProcessId -> String -> Process (Maybe Message)
128−safeFetch pid key = do
129− (sp, rp) <- newChan
130− send pid $ Get key sp
131− withMonitor pid $ do
132− receiveWait [
133− matchChan rp return
134− , matchIf (\(ProcessMonitorNotification _ pid' _) -> pid' == pid)
135− (\_ -> return $ Just (unsafeCreateUnencodedMessage ()))
136− ]
137−
138−sendReq :: MxTableRequest -> MxTableId -> Process ()
139−sendReq req tid = (resolve tid) req
140−
141−resolve :: Serializable a => MxTableId -> (a -> Process ())
142−resolve (MxForAgent agent) = \msg -> nsend mxTableCoordinator (agent, msg)
143−resolve (MxForPid pid) = \msg -> send pid msg
144−
145−startTableCoordinator :: Fork -> Process ()
146−startTableCoordinator fork = run Map.empty
147− where
148− run :: MxTables -> Process ()
149− run tables =
150− receiveWait [
151− -- note that this state change can race with MxAgentStart requests
152− match (\(ProcessMonitorNotification _ pid _) -> do
153− return $ Map.filter (/= pid) tables)
154− , match (\(MxAgentStart ch agent) -> do
155− lookupAgent tables agent >>= \(p, t) -> do
156− sendChan ch p >> return t)
157− , match (\req@(agent, tReq :: MxTableRequest) -> do
158− case tReq of
159− Get k sp -> do
160− lookupAgent tables agent >>= \(p, t) -> do
161− safeFetch p k >>= sendChan sp >> return t
162− _ -> do
163− handleRequest tables req)
164− , matchAny (\_ -> return tables) -- unrecognised messages are dropped
165− ] >>= run
166−
167− handleRequest :: MxTables
168− -> (MxAgentId, MxTableRequest)
169− -> Process MxTables
170− handleRequest tables' (agent, req) = do
171− lookupAgent tables' agent >>= \(p, t) -> send p req >> return t
172−
173− lookupAgent :: MxTables -> MxAgentId -> Process (ProcessId, MxTables)
174− lookupAgent tables' agentId' = do
175− case Map.lookup agentId' tables' of
176− Nothing -> launchNew agentId' tables'
177− Just p -> return (p, tables')
178−
179− launchNew :: MxAgentId
180− -> MxTables
181− -> Process (ProcessId, MxTables)
182− launchNew mxId tblMap = do
183− let initState = MxTableState { _name = (agentId mxId)
184− , _entries = Map.empty
185− }
186− (pid, _) <- spawnSup $ tableHandler initState
187− return $ (pid, mxId `seq` pid `seq` Map.insert mxId pid tblMap)
188−
189− spawnSup proc = do
190− us <- getSelfPid
191− -- we need to use that passed in "fork", in order to
192− -- break an import cycle with Node.hs courtesy of the
193− -- management agent, API and tracing modules
194− them <- liftIO $ fork $ link us >> proc
195− ref <- monitor them
196− return (them, ref)
197−
198−tableHandler :: MxTableState -> Process ()
199−tableHandler state = do
200− ns <- receiveWait [
201− match (handleTableRequest state)
202− , matchAny (\_ -> return (Just state))
203− ]
204− case ns of
205− Nothing -> return ()
206− Just s' -> tableHandler s'
207− where
208− handleTableRequest _ Delete = return Nothing
209− handleTableRequest st Purge = return $ Just $ (entries ^= Map.empty) $ st
210− handleTableRequest st (Clear k) = return $ Just $ (entries ^: (k `seq` Map.delete k)) $ st
211− handleTableRequest st (Set k v) = return $ Just $ (entries ^: (k `seq` v `seq` Map.insert k v)) st
212− handleTableRequest st (Get k c) = getEntry k c st >> return (Just st)
213−
214−getEntry :: String
215− -> SendPort (Maybe Message)
216− -> MxTableState
217− -> Process ()
218−getEntry k m MxTableState{..} = do
219− sendChan m =<< return (Map.lookup k _entries)
220−
221−entries :: Accessor MxTableState (Map String Message)
222−entries = accessor _entries (\ls st -> st { _entries = ls })
src/Control/Distributed/Process/Management/Internal/Trace/Primitives.hs+10−3
@@ -28,11 +28,12 @@ module Control.Distributed.Process.Management.Internal.Trace.Primitives
2828 , withRegisteredTracer
2929 ) where
3030
31−import Control.Applicative ((<$>))
31+import Control.Applicative
3232import Control.Distributed.Process.Internal.Primitives
3333 ( whereis
3434 , newChan
3535 , receiveChan
36+ , die
3637 )
3738import Control.Distributed.Process.Management.Internal.Trace.Types
3839 ( TraceArg(..)
@@ -67,6 +68,7 @@ import Control.Monad.IO.Class (liftIO)
6768import Control.Monad.Reader (ask)
6869
6970import qualified Data.Set as Set (fromList)
71+import Prelude
7072
7173--------------------------------------------------------------------------------
7274-- Main API --
@@ -167,6 +169,11 @@ withRegisteredTracer act = do
167169 withLocalTracer $ \t -> liftIO $ Tracer.getCurrentTraceClient t sp
168170 currentTracer <- receiveChan rp
169171 case currentTracer of
170− Nothing -> do { (Just p') <- whereis "tracer.initial"; act p' }
172+ Nothing -> do mTP <- whereis "tracer.initial"
173+ -- NB: this should NOT ever happen, but forcing pattern matches
174+ -- is not considered cool in later versions of MonadFail
175+ case mTP of
176+ Just p' -> act p'
177+ Nothing -> die $ "System Invariant Violation: Tracer Process "
178+ ++ "Name Not Found (whereis tracer.initial)"
171179 (Just p) -> act p
172−
src/Control/Distributed/Process/Management/Internal/Trace/Tracer.hs+12−5
@@ -11,7 +11,7 @@ module Control.Distributed.Process.Management.Internal.Trace.Tracer
1111 , eventLogTracer
1212 ) where
1313
14−import Control.Applicative ((<$>))
14+import Control.Applicative
1515import Control.Concurrent.Chan (writeChan)
1616import Control.Concurrent.MVar
1717 ( MVar
@@ -21,9 +21,7 @@ import Control.Distributed.Process.Internal.CQueue
2121 ( CQueue
2222 )
2323import Control.Distributed.Process.Internal.Primitives
24− ( catch
25− , finally
26− , die
24+ ( die
2725 , receiveWait
2826 , forward
2927 , sendChan
@@ -63,6 +61,10 @@ import Control.Distributed.Process.Internal.Types
6361
6462import Control.Monad.IO.Class (liftIO)
6563import Control.Monad.Reader (ask)
64+import Control.Monad.Catch
65+ ( catch
66+ , finally
67+ )
6668
6769import Data.Set (Set)
6870import qualified Data.Set as Set
@@ -73,6 +75,7 @@ import Data.Maybe (fromMaybe)
7375import Data.Time.Clock (getCurrentTime)
7476import Data.Time.Format (formatTime)
7577import Debug.Trace (traceEventIO)
78+import Prelude
7679
7780#if ! MIN_VERSION_base(4,6,0)
7881import Prelude hiding (catch)
@@ -88,7 +91,11 @@ import System.IO
8891 , hPutStrLn
8992 , hSetBuffering
9093 )
94+#if MIN_VERSION_time(1,5,0)
95+import Data.Time.Format (defaultTimeLocale)
96+#else
9197import System.Locale (defaultTimeLocale)
98+#endif
9299import System.Mem.Weak
93100 ( Weak
94101 )
@@ -146,7 +153,7 @@ systemLoggerTracer = do
146153 emptyPid <- return $ (nullProcessId (localNodeId node))
147154 traceMsg <- return $ NCMsg {
148155 ctrlMsgSender = ProcessIdentifier (emptyPid)
149− , ctrlMsgSignal = (NamedSend "logger"
156+ , ctrlMsgSignal = (NamedSend "trace.logger"
150157 (createUnencodedMessage msg))
151158 }
152159 liftIO $ writeChan (localCtrlChan node) traceMsg
src/Control/Distributed/Process/Management/Internal/Trace/Types.hs−10
@@ -43,7 +43,6 @@ import Control.Distributed.Process.Serializable
4343import Data.Binary
4444import Data.List (intersperse)
4545import Data.Set (Set)
46−import qualified Data.Set as Set (fromList)
4746import Data.Typeable
4847import GHC.Generics
4948
@@ -158,12 +157,3 @@ getTraceFlags t s = publishEvent t (unsafeCreateUnencodedMessage s)
158157
159158getCurrentTraceClient :: MxEventBus -> SendPort (Maybe ProcessId) -> IO ()
160159getCurrentTraceClient t s = publishEvent t (unsafeCreateUnencodedMessage s)
161−
162−class Traceable a where
163− uod :: [a] -> TraceSubject
164−
165−instance Traceable ProcessId where
166− uod = TraceProcs . Set.fromList
167−
168−instance Traceable String where
169− uod = TraceNames . Set.fromList
src/Control/Distributed/Process/Management/Internal/Types.hs+9−19
@@ -1,16 +1,13 @@
11{-# LANGUAGE DeriveDataTypeable #-}
2−{-# LANGUAGE StandaloneDeriving #-}
32{-# LANGUAGE ExistentialQuantification #-}
43{-# LANGUAGE GeneralizedNewtypeDeriving #-}
54{-# LANGUAGE DeriveGeneric #-}
65module Control.Distributed.Process.Management.Internal.Types
76 ( MxAgentId(..)
8− , MxTableId(..)
97 , MxAgentState(..)
108 , MxAgent(..)
119 , MxAction(..)
1210 , ChannelSelector(..)
13− , MxAgentStart(..)
1411 , Fork
1512 , MxSink
1613 , MxEvent(..)
@@ -24,8 +21,8 @@ import Control.Concurrent.STM
2421import Control.Distributed.Process.Internal.Types
2522 ( Process
2623 , ProcessId
24+ , SendPortId
2725 , Message
28− , SendPort
2926 , DiedReason
3027 , NodeId
3128 )
@@ -34,6 +31,7 @@ import qualified Control.Monad.State as ST
3431 ( MonadState
3532 , StateT
3633 )
34+import Control.Monad.Fix (MonadFix)
3735import Data.Binary
3836import Data.Typeable (Typeable)
3937import GHC.Generics
@@ -60,8 +58,14 @@ data MxEvent =
6058 -- ^ fired whenever a node /dies/ (i.e., the connection is broken/disconnected)
6159 | MxSent ProcessId ProcessId Message
6260 -- ^ fired whenever a message is sent from a local process
61+ | MxSentToName String ProcessId Message
62+ -- ^ fired whenever a named send occurs
63+ | MxSentToPort ProcessId SendPortId Message
64+ -- ^ fired whenever a sendChan occurs
6365 | MxReceived ProcessId Message
6466 -- ^ fired whenever a message is received by a local process
67+ | MxReceivedPort SendPortId Message
68+ -- ^ fired whenever a message is received via a typed channel
6569 | MxConnected ConnectionId EndPointAddress
6670 -- ^ fired when a network-transport connection is first established
6771 | MxDisconnected ConnectionId EndPointAddress
@@ -100,17 +104,10 @@ type Fork = (Process () -> IO ProcessId)
100104newtype MxAgentId = MxAgentId { agentId :: String }
101105 deriving (Typeable, Binary, Eq, Ord)
102106
103−data MxTableId =
104− MxForAgent !MxAgentId
105− | MxForPid !ProcessId
106− deriving (Typeable, Generic)
107−instance Binary MxTableId where
108−
109107data MxAgentState s = MxAgentState
110108 {
111109 mxAgentId :: !MxAgentId
112110 , mxBus :: !(TChan Message)
113− , mxSharedTable :: !ProcessId
114111 , mxLocalState :: !s
115112 }
116113
@@ -123,19 +120,12 @@ newtype MxAgent s a =
123120 } deriving ( Functor
124121 , Monad
125122 , MonadIO
123+ , MonadFix
126124 , ST.MonadState (MxAgentState s)
127125 , Typeable
128126 , Applicative
129127 )
130128
131−data MxAgentStart = MxAgentStart
132− {
133− mxAgentTableChan :: SendPort ProcessId
134− , mxAgentIdStart :: MxAgentId
135− }
136− deriving (Typeable, Generic)
137−instance Binary MxAgentStart where
138−
139129data ChannelSelector = InputChan | Mailbox
140130
141131-- | Represents the actions a management agent can take
src/Control/Distributed/Process/Node.hs+311−198
@@ -5,6 +5,7 @@
55{-# LANGUAGE BangPatterns #-}
66{-# LANGUAGE GeneralizedNewtypeDeriving #-}
77{-# LANGUAGE RecordWildCards #-}
8+{-# LANGUAGE MagicHash #-}
89
910-- | Local nodes
1011--
@@ -20,10 +21,6 @@ module Control.Distributed.Process.Node
2021
2122-- TODO: Calls to 'sendBinary' and co (by the NC) may stall the node controller.
2223
23−#if ! MIN_VERSION_base(4,6,0)
24−import Prelude hiding (catch)
25−#endif
26−
2724import System.IO (fixIO, hPutStrLn, stderr)
2825import System.Mem.Weak (Weak, deRefWeak)
2926import qualified Data.ByteString.Lazy as BSL (fromChunks)
@@ -31,52 +28,70 @@ import Data.Binary (decode)
3128import Data.Map (Map)
3229import qualified Data.Map as Map
3330 ( empty
31+ , delete
3432 , toList
3533 , fromList
36− , filter
34+ , insert
35+ , lookup
36+ , partition
3737 , partitionWithKey
3838 , elems
3939 , size
4040 , filterWithKey
4141 , foldlWithKey
4242 )
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
4349import Data.Set (Set)
4450import qualified Data.Set as Set
4551 ( empty
4652 , insert
4753 , delete
54+ , map
4855 , member
4956 , toList
57+ , union
5058 )
5159import Data.Foldable (forM_)
5260import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
5361import Data.Typeable (Typeable)
5462import Control.Category ((>>>))
55−import Control.Applicative (Applicative, (<$>))
56−import Control.Monad (void, when)
63+import Control.Applicative
64+import Control.Monad (void, when, join)
5765import Control.Monad.IO.Class (MonadIO, liftIO)
5866import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
5967import qualified Control.Monad.State.Strict as StateT (get, put)
6068import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
6169import Control.Exception
6270 ( throwIO
63− , AsyncException(ThreadKilled)
6471 , SomeException
6572 , Exception
6673 , throwTo
6774 , uninterruptibleMask_
75+ , getMaskingState
76+ , MaskingState(..)
6877 )
69−import qualified Control.Exception as Exception (Handler(..), catches, finally)
70−import Control.Concurrent (forkIO, forkIOWithUnmask, myThreadId)
78+import qualified Control.Exception as Exception
79+ ( Handler(..)
80+ , catch
81+ , catches
82+ , finally
83+ )
84+import Control.Concurrent (forkIO, killThread)
85+import Control.Distributed.Process.Internal.BiMultiMap (BiMultiMap)
86+import qualified Control.Distributed.Process.Internal.BiMultiMap as BiMultiMap
7187import Control.Distributed.Process.Internal.StrictMVar
7288 ( newMVar
7389 , withMVar
7490 , modifyMVarMasked
75− , modifyMVar_
91+ , modifyMVar
7692 , newEmptyMVar
7793 , putMVar
7894 , takeMVar
79− , readMVar
8095 )
8196import Control.Concurrent.Chan (newChan, writeChan, readChan)
8297import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
@@ -88,6 +103,7 @@ import Control.Distributed.Process.Internal.CQueue
88103 , enqueue
89104 , newCQueue
90105 , mkWeakCQueue
106+ , queueSize
91107 )
92108import qualified Network.Transport as NT
93109 ( Transport
@@ -99,6 +115,7 @@ import qualified Network.Transport as NT
99115 , TransportError(..)
100116 , address
101117 , closeEndPoint
118+ , Connection
102119 , ConnectionId
103120 , close
104121 , EndPointAddress
@@ -118,6 +135,9 @@ import Control.Distributed.Process.Internal.Types
118135 , LocalNode(..)
119136 , MxEventBus(..)
120137 , LocalNodeState(..)
138+ , ValidLocalNodeState(..)
139+ , withValidLocalState
140+ , modifyValidLocalState
121141 , LocalProcess(..)
122142 , LocalProcessState(..)
123143 , Process(..)
@@ -131,6 +151,7 @@ import Control.Distributed.Process.Internal.Types
131151 , localConnections
132152 , forever'
133153 , MonitorRef(..)
154+ , NodeClosedException(..)
134155 , ProcessMonitorNotification(..)
135156 , NodeMonitorNotification(..)
136157 , PortMonitorNotification(..)
@@ -157,6 +178,7 @@ import Control.Distributed.Process.Internal.Types
157178 , WhereIsReply(..)
158179 , payloadToMessage
159180 , createUnencodedMessage
181+ , unsafeCreateUnencodedMessage
160182 , runLocalProcess
161183 , firstNonReservedProcessId
162184 , ImplicitReconnect(WithImplicitReconnect)
@@ -164,9 +186,8 @@ import Control.Distributed.Process.Internal.Types
164186import Control.Distributed.Process.Management.Internal.Agent
165187 ( mxAgentController
166188 )
167−import qualified Control.Distributed.Process.Management.Internal.Table as Table
168− ( mxTableCoordinator
169− , startTableCoordinator
189+import Control.Distributed.Process.Management.Internal.Types
190+ ( MxEvent(..)
170191 )
171192import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
172193 ( remoteTable
@@ -180,24 +201,22 @@ import Control.Distributed.Process.Management.Internal.Trace.Types
180201 , traceLogFmt
181202 , enableTrace
182203 )
183−import Control.Distributed.Process.Management.Internal.Types
184− ( MxEvent(..)
185− )
186204import Control.Distributed.Process.Serializable (Serializable)
187205import Control.Distributed.Process.Internal.Messaging
188206 ( sendBinary
189− , sendMessage
190207 , closeImplicitReconnections
191208 , impliesDeathOf
192209 )
193210import Control.Distributed.Process.Internal.Primitives
194211 ( register
195− , finally
196212 , receiveWait
197213 , match
198214 , sendChan
199− , catch
200215 , unwrapMessage
216+ , monitor
217+ , unmonitorAsync
218+ , getSelfNode
219+ , SayMessage(..)
201220 )
202221import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
203222import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
@@ -206,7 +225,20 @@ import qualified Control.Distributed.Process.Internal.StrictContainerAccessors a
206225 ( mapMaybe
207226 , mapDefault
208227 )
228+import Control.Monad.Catch (try)
229+import GHC.IO (IO(..), unsafeUnmask)
230+import GHC.Base ( maskAsyncExceptions# )
231+
209232import 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
210242
211243--------------------------------------------------------------------------------
212244-- Initialization --
@@ -230,7 +262,7 @@ newLocalNode transport rtable = do
230262createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
231263createBareLocalNode endPoint rtable = do
232264 unq <- randomIO
233− state <- newMVar LocalNodeState
265+ state <- newMVar $ LocalNodeValid $ ValidLocalNodeState
234266 { _localProcesses = Map.empty
235267 , _localPidCounter = firstNonReservedProcessId
236268 , _localPidUnique = unq
@@ -292,17 +324,19 @@ startServiceProcesses node = do
292324 -- before /that/ process has started - this is a totally harmless race
293325 -- however, so we deliberably ignore it
294326 startDefaultTracer node
295− tableCoordinatorPid <- fork $ Table.startTableCoordinator fork
296− runProcess node $ register Table.mxTableCoordinator tableCoordinatorPid
297327 logger <- forkProcess node loop
298− runProcess node $ register "logger" logger
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
299334 where
300− fork = forkProcess node
301−
302335 loop = do
303336 receiveWait
304− [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
305− liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
337+ [ match $ \(SayMessage time pid string) -> do
338+ let time' = formatTime defaultTimeLocale "%c" time
339+ liftIO . hPutStrLn stderr $ time' ++ " " ++ show pid ++ ": " ++ string
306340 loop
307341 , match $ \((time, string) :: (String, String)) -> do
308342 -- this is a 'trace' message from the local node tracer
@@ -312,33 +346,48 @@ startServiceProcesses node = do
312346 sendChan ch ()
313347 ]
314348
315−-- | Force-close a local node
316−--
317−-- TODO: for now we just close the associated endpoint
349+-- | Force-close a local node, killing all processes on that node.
318350closeLocalNode :: LocalNode -> IO ()
319−closeLocalNode node =
320− -- TODO: close all our processes, surely!?
351+closeLocalNode node = do
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').
321368 NT.closeEndPoint (localEndPoint node)
322369
323370-- | Run a process on a local node and wait for it to finish
324371runProcess :: LocalNode -> Process () -> IO ()
325372runProcess node proc = do
326373 done <- newEmptyMVar
327− tid <- myThreadId
328− void $ forkProcess node $ do
329− catch (proc `finally` liftIO (putMVar done ()))
330− (\(ex :: SomeException) -> liftIO $ throwTo tid ex)
331− takeMVar done
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
377+ takeMVar done >>= either (throwIO :: SomeException -> IO a) return
332378
333379-- | Spawn a new process on a local node
334380forkProcess :: LocalNode -> Process () -> IO ProcessId
335−forkProcess node proc =
336− modifyMVarMasked (localState node) startProcess
381+forkProcess node proc = do
382+ ms <- getMaskingState
383+ modifyMVarMasked (localState node) (startProcess ms)
337384 where
338− startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
339− startProcess st = do
340− let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
341− , lpidUnique = st ^. localPidUnique
385+ startProcess :: MaskingState
386+ -> LocalNodeState
387+ -> IO (LocalNodeState, ProcessId)
388+ startProcess ms (LocalNodeValid vst) = do
389+ let lpid = LocalProcessId { lpidCounter = vst ^. localPidCounter
390+ , lpidUnique = vst ^. localPidUnique
342391 }
343392 let pid = ProcessId { processNodeId = localNodeId node
344393 , processLocalId = lpid
@@ -358,7 +407,13 @@ forkProcess node proc =
358407 , processThread = tid
359408 , processNode = node
360409 }
361− tid' <- uninterruptibleMask_ $ forkIOWithUnmask $ \unmask -> do
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
362417 reason <- Exception.catches
363418 (unmask $ runLocalProcess lproc proc >> return DiedNormal)
364419 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
@@ -370,7 +425,12 @@ forkProcess node proc =
370425 (return . DiedException . (show :: SomeException -> String)))]
371426
372427 -- [Unified: Table 4, rules termination and exiting]
373− modifyMVar_ (localState node) (cleanupProcess pid)
428+ mconns <- modifyValidLocalState 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+
374434 writeChan (localCtrlChan node) NCMsg
375435 { ctrlMsgSender = ProcessIdentifier pid
376436 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
@@ -385,27 +445,34 @@ forkProcess node proc =
385445 -- TODO: this doesn't look right at all - how do we know
386446 -- that newUnique represents a process id that is available!?
387447 newUnique <- randomIO
388− return ( (localProcessWithId lpid ^= Just lproc)
448+ return ( LocalNodeValid
449+ $ (localProcessWithId lpid ^= Just lproc)
389450 . (localPidCounter ^= firstNonReservedProcessId)
390451 . (localPidUnique ^= newUnique)
391− $ st
452+ $ vst
392453 , pid
393454 )
394455 else
395− return ( (localProcessWithId lpid ^= Just lproc)
456+ return ( LocalNodeValid
457+ $ (localProcessWithId lpid ^= Just lproc)
396458 . (localPidCounter ^: (+ 1))
397− $ st
459+ $ vst
398460 , pid
399461 )
462+ startProcess _ LocalNodeClosed =
463+ throwIO $ NodeClosedException $ localNodeId node
400464
401− cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
402− cleanupProcess pid st = do
465+ cleanupProcess :: ProcessId
466+ -> ValidLocalNodeState
467+ -> IO (ValidLocalNodeState, [NT.Connection])
468+ cleanupProcess pid vst = do
403469 let pid' = ProcessIdentifier pid
404− let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
405− mapM_ (NT.close . fst) (Map.elems affected)
406− return $ (localProcessWithId (processLocalId pid) ^= Nothing)
407− . (localConnections ^= unaffected)
408− $ st
470+ let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (vst ^. localConnections)
471+ return ( (localProcessWithId (processLocalId pid) ^= Nothing)
472+ . (localConnections ^= unaffected)
473+ $ vst
474+ , map fst $ Map.elems affected
475+ )
409476
410477-- note [tracer/forkProcess races]
411478--
@@ -425,7 +492,7 @@ type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
425492data IncomingTarget =
426493 Uninit
427494 | ToProc ProcessId (Weak (CQueue Message))
428− | ToChan TypedChannel
495+ | ToChan SendPortId TypedChannel
429496 | ToNode
430497
431498data ConnectionState = ConnectionState {
@@ -452,6 +519,7 @@ incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
452519
453520handleIncomingMessages :: LocalNode -> IO ()
454521handleIncomingMessages node = go initConnectionState
522+ `Exception.catch` \(NodeClosedException _) -> return ()
455523 where
456524 go :: ConnectionState -> IO ()
457525 go !st = do
@@ -466,7 +534,8 @@ handleIncomingMessages node = go initConnectionState
466534 . (incomingFrom theirAddr ^: Set.insert cid)
467535 $ st
468536 )
469− else invalidRequest cid st
537+ else invalidRequest cid st $
538+ "attempt to connect with unsupported reliability " ++ show rel
470539 NT.Received cid payload ->
471540 case st ^. incomingAt cid of
472541 Just (_, ToProc pid weakQueue) -> do
@@ -478,13 +547,17 @@ handleIncomingMessages node = go initConnectionState
478547 enqueue queue msg -- 'enqueue' is strict
479548 trace node (MxReceived pid msg)
480549 go st
481− Just (_, ToChan (TypedChannel chan')) -> do
550+ Just (_, ToChan chId (TypedChannel chan')) -> do
482551 mChan <- deRefWeak chan'
483552 -- If mChan is Nothing, the process has given up the read end of
484553 -- the channel and we simply ignore the incoming message
485− forM_ mChan $ \chan -> atomically $
486− -- We make sure the message is fully decoded when it is enqueued
487− writeTQueue chan $! decode (BSL.fromChunks payload)
554+ forM_ mChan $ \chan -> do
555+ msg' <- atomically $ do
556+ msg <- return $! decode (BSL.fromChunks payload)
557+ -- We make sure the message is fully decoded when it is enqueued
558+ writeTQueue chan msg
559+ return msg
560+ trace node $ MxReceivedPort chId $ unsafeCreateUnencodedMessage msg'
488561 go st
489562 Just (_, ToNode) -> do
490563 let ctrlMsg = decode . BSL.fromChunks $ payload
@@ -494,36 +567,47 @@ handleIncomingMessages node = go initConnectionState
494567 case decode (BSL.fromChunks payload) of
495568 ProcessIdentifier pid -> do
496569 let lpid = processLocalId pid
497− mProc <- withMVar state $ return . (^. localProcessWithId lpid)
570+ mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
498571 case mProc of
499572 Just proc ->
500573 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
501574 Nothing ->
502− invalidRequest cid st
575+ -- incoming attempt to connect to unknown process - might
576+ -- be dead already
577+ go (incomingAt cid ^= Nothing $ st)
503578 SendPortIdentifier chId -> do
504579 let lcid = sendPortLocalId chId
505580 lpid = processLocalId (sendPortProcessId chId)
506− mProc <- withMVar state $ return . (^. localProcessWithId lpid)
581+ mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
507582 case mProc of
508583 Just proc -> do
509584 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
510585 case mChannel of
511586 Just channel ->
512− go (incomingAt cid ^= Just (src, ToChan channel) $ st)
587+ go (incomingAt cid ^= Just (src, ToChan chId channel) $ st)
513588 Nothing ->
514− invalidRequest cid st
589+ invalidRequest cid st $
590+ "incoming attempt to connect to unknown channel of"
591+ ++ " process " ++ show (sendPortProcessId chId)
515592 Nothing ->
516− invalidRequest cid st
593+ -- incoming attempt to connect to channel of unknown
594+ -- process - might be dead already
595+ go (incomingAt cid ^= Nothing $ st)
517596 NodeIdentifier nid ->
518597 if nid == localNodeId node
519598 then go (incomingAt cid ^= Just (src, ToNode) $ st)
520− else invalidRequest cid st
599+ 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
521604 Nothing ->
522605 invalidRequest cid st
606+ "message received from an unknown connection"
523607 NT.ConnectionClosed cid ->
524608 case st ^. incomingAt cid of
525609 Nothing ->
526− invalidRequest cid st
610+ invalidRequest cid st "closed unknown connection"
527611 Just (src, _) -> do
528612 trace node (MxDisconnected cid src)
529613 go ( (incomingAt cid ^= Nothing)
@@ -554,19 +638,20 @@ handleIncomingMessages node = go initConnectionState
554638 -- and we just give up
555639 fail "Cloud Haskell fatal error: received unexpected multicast"
556640
557− invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
558− invalidRequest cid st = do
641+ invalidRequest :: NT.ConnectionId -> ConnectionState -> String -> IO ()
642+ invalidRequest cid st msg = do
559643 -- TODO: We should treat this as a fatal error on the part of the remote
560644 -- node. That is, we should report the remote node as having died, and we
561645 -- should close incoming connections (this requires a Transport layer
562646 -- extension).
563− traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
564− (Trace cid)]
647+ traceEventFmtIO node "" [ TraceStr $ " [network] invalid request"
648+ ++ " (" ++ msg ++ "): "
649+ , (Trace cid)
650+ ]
565651 go ( incomingAt cid ^= Nothing
566652 $ st
567653 )
568654
569− state = localState node
570655 endpoint = localEndPoint node
571656 ctrlChan = localCtrlChan node
572657
@@ -575,8 +660,9 @@ handleIncomingMessages node = go initConnectionState
575660--------------------------------------------------------------------------------
576661
577662runNodeController :: LocalNode -> IO ()
578−runNodeController =
579− runReaderT (evalStateT (unNC nodeController) initNCState)
663+runNodeController node =
664+ runReaderT (evalStateT (unNC nodeController) initNCState) node
665+ `Exception.catch` \(NodeClosedException _) -> return ()
580666
581667--------------------------------------------------------------------------------
582668-- Internal data types --
@@ -584,9 +670,9 @@ runNodeController =
584670
585671data NCState = NCState
586672 { -- Mapping from remote processes to linked local processes
587− _links :: !(Map Identifier (Set ProcessId))
673+ _links :: !(BiMultiMap Identifier ProcessId ())
588674 -- Mapping from remote processes to monitoring local processes
589− , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
675+ , _monitors :: !(BiMultiMap Identifier ProcessId MonitorRef)
590676 -- Process registry: names and where they live, mapped to the PIDs
591677 , _registeredHere :: !(Map String ProcessId)
592678 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
@@ -602,8 +688,8 @@ newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
602688 )
603689
604690initNCState :: NCState
605−initNCState = NCState { _links = Map.empty
606− , _monitors = Map.empty
691+initNCState = NCState { _links = BiMultiMap.empty
692+ , _monitors = BiMultiMap.empty
607693 , _registeredHere = Map.empty
608694 , _registeredOnNodes = Map.empty
609695 }
@@ -619,6 +705,33 @@ instance Show ProcessKillException where
619705 show (ProcessKillException pid reason) =
620706 "killed-by=" ++ show pid ++ ",reason=" ++ reason
621707
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+
622735--------------------------------------------------------------------------------
623736-- Tracing/Debugging --
624737--------------------------------------------------------------------------------
@@ -661,11 +774,7 @@ nodeController = do
661774 -- [Unified: Table 7, rule nc_forward]
662775 case destNid (ctrlMsgSignal msg) of
663776 Just nid' | nid' /= localNodeId node ->
664− liftIO $ sendBinary node
665− (ctrlMsgSender msg)
666− (NodeIdentifier nid')
667− WithImplicitReconnect
668− msg
777+ ncSendToNode nid' msg
669778 _ ->
670779 return ()
671780
@@ -688,6 +797,8 @@ nodeController = do
688797 ncEffectWhereIs from label
689798 NCMsg _ (NamedSend label msg') ->
690799 ncEffectNamedSend label msg'
800+ NCMsg _ (UnreliableSend lpid msg') ->
801+ ncEffectLocalSend node (ProcessId (localNodeId node) lpid) msg'
691802 NCMsg _ (LocalSend to msg') ->
692803 ncEffectLocalSend node to msg'
693804 NCMsg _ (LocalPortSend to msg') ->
@@ -701,8 +812,7 @@ nodeController = do
701812 NCMsg _ SigShutdown ->
702813 liftIO $ do
703814 NT.closeEndPoint (localEndPoint node)
704− `Exception.finally` throwIO ThreadKilled
705− -- ThreadKilled seems to make more sense than fail/error here
815+ `Exception.finally` throwIO (NodeClosedException $ localNodeId node)
706816 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
707817 ncEffectGetNodeStats from nid
708818 unexpected ->
@@ -722,22 +832,18 @@ ncEffectMonitor from them mRef = do
722832 case (shouldLink, isLocal node (ProcessIdentifier from)) of
723833 (True, _) -> -- [Unified: first rule]
724834 case mRef of
725− Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
726− Nothing -> modify' $ linksFor them ^: Set.insert from
835+ Just ref -> modify' $ monitors ^: BiMultiMap.insert them from ref
836+ Nothing -> modify' $ links ^: BiMultiMap.insert them from ()
727837 (False, True) -> -- [Unified: second rule]
728838 notifyDied from them DiedUnknownId mRef
729839 (False, False) -> -- [Unified: third rule]
730840 -- TODO: this is the right sender according to the Unified semantics,
731841 -- but perhaps having 'them' as the sender would make more sense
732842 -- (see also: notifyDied)
733− liftIO $ sendBinary node
734− (NodeIdentifier $ localNodeId node)
735− (NodeIdentifier $ processNodeId from)
736− WithImplicitReconnect
737− NCMsg
738− { ctrlMsgSender = NodeIdentifier (localNodeId node)
739− , ctrlMsgSignal = Died them DiedUnknownId
740− }
843+ ncSendToNode (processNodeId from) $ NCMsg
844+ { ctrlMsgSender = NodeIdentifier (localNodeId node)
845+ , ctrlMsgSignal = Died them DiedUnknownId
846+ }
741847
742848-- [Unified: Table 11]
743849ncEffectUnlink :: ProcessId -> Identifier -> NC ()
@@ -751,7 +857,7 @@ ncEffectUnlink from them = do
751857 postAsMessage from $ DidUnlinkNode nid
752858 SendPortIdentifier cid ->
753859 postAsMessage from $ DidUnlinkPort cid
754− modify' $ linksFor them ^: Set.delete from
860+ modify' $ links ^: BiMultiMap.delete them from ()
755861
756862-- [Unified: Table 11]
757863ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
@@ -759,7 +865,7 @@ ncEffectUnmonitor from ref = do
759865 node <- ask
760866 when (isLocal node (ProcessIdentifier from)) $
761867 postAsMessage from $ DidUnmonitor ref
762− modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
868+ modify' $ monitors ^: BiMultiMap.delete (monitorRefIdent ref) from ref
763869
764870-- [Unified: Table 12]
765871ncEffectDied :: Identifier -> DiedReason -> NC ()
@@ -774,7 +880,7 @@ ncEffectDied ident reason = do
774880 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
775881
776882 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
777− forM_ uss $ \us ->
883+ forM_ uss $ \(us, _) ->
778884 when (localOnly <= isLocal node (ProcessIdentifier us)) $
779885 notifyDied us them reason Nothing
780886
@@ -783,9 +889,35 @@ ncEffectDied ident reason = do
783889 when (localOnly <= isLocal node (ProcessIdentifier us)) $
784890 notifyDied us them reason (Just ref)
785891
786− modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
787−
788− modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
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')
915+
916+ -- we now consider all labels for this identifier unregistered
917+ let toDrop 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
789921
790922 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
791923 mapM (\(pid,nidlist) ->
@@ -793,20 +925,15 @@ ncEffectDied ident reason = do
793925 True ->
794926 do forM_ nidlist $ \(nid,_) ->
795927 when (not $ isLocal node (NodeIdentifier nid))
796− (forwardNameDeath node nid)
928+ (forwardDeath node nid)
797929 return Nothing
798930 False -> return $ Just (pid,nidlist) )
799931 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
800932 where
801− forwardNameDeath node nid =
802− liftIO $ sendBinary node
803− (NodeIdentifier $ localNodeId node)
804− (NodeIdentifier $ nid)
805− WithImplicitReconnect
806− NCMsg
807− { ctrlMsgSender = NodeIdentifier (localNodeId node)
808− , ctrlMsgSignal = Died ident reason
809− }
933+ forwardDeath node nid = ncSendToNode nid
934+ NCMsg { ctrlMsgSender = NodeIdentifier (localNodeId node)
935+ , ctrlMsgSignal = Died ident reason
936+ }
810937
811938-- [Unified: Table 13]
812939ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
@@ -820,11 +947,7 @@ ncEffectSpawn pid cProc ref = do
820947 Right p -> p
821948 node <- ask
822949 pid' <- liftIO $ forkProcess node proc
823− liftIO $ sendMessage node
824− (NodeIdentifier (localNodeId node))
825− (ProcessIdentifier pid)
826− WithImplicitReconnect
827− (DidSpawn ref pid')
950+ ncSendToProcess pid $ unsafeCreateUnencodedMessage $ DidSpawn ref pid'
828951
829952-- Unified semantics does not explicitly describe how to implement 'register',
830953-- but mentions it's "very similar to nsend" (Table 14)
@@ -842,17 +965,19 @@ ncEffectRegister from label atnode mPid reregistration = do
842965 return $ (isNothing currentVal /= reregistration) &&
843966 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
844967 if isLocal node (NodeIdentifier atnode)
845− then do when (isOk) $
968+ then do when isOk $
846969 do modify' $ registeredHereFor label ^= mPid
847970 updateRemote node currentVal mPid
848971 case mPid of
849− (Just p) -> liftIO $ trace node (MxRegistered p label)
972+ (Just p) -> do
973+ if reregistration
974+ then liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
975+ else return ()
976+ liftIO $ trace node (MxRegistered p label)
850977 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
851− liftIO $ sendMessage node
852− (NodeIdentifier (localNodeId node))
853− (ProcessIdentifier from)
854− WithImplicitReconnect
855− (RegisterReply label isOk)
978+ newVal <- gets (^. registeredHereFor label)
979+ ncSendToProcess from $ unsafeCreateUnencodedMessage $
980+ RegisterReply label isOk newVal
856981 else let operation =
857982 case reregistration of
858983 True -> flip decList
@@ -882,40 +1007,36 @@ ncEffectRegister from label atnode mPid reregistration = do
8821007 decList (x:xs) tag = x:decList xs tag
8831008 forward node to reg =
8841009 when (not $ isLocal node (NodeIdentifier to)) $
885− liftIO $ sendBinary node
886− (ProcessIdentifier from)
887− (NodeIdentifier to)
888− WithImplicitReconnect
889− NCMsg
890− { ctrlMsgSender = ProcessIdentifier from
891− , ctrlMsgSignal = reg
892− }
1010+ ncSendToNode to $ NCMsg { ctrlMsgSender = ProcessIdentifier from
1011+ , ctrlMsgSignal = reg
1012+ }
8931013
8941014
8951015-- Unified semantics does not explicitly describe 'whereis'
8961016ncEffectWhereIs :: ProcessId -> String -> NC ()
8971017ncEffectWhereIs from label = do
898− node <- ask
8991018 mPid <- gets (^. registeredHereFor label)
900− liftIO $ sendMessage node
901− (NodeIdentifier (localNodeId node))
902− (ProcessIdentifier from)
903− WithImplicitReconnect
904− (WhereIsReply label mPid)
1019+ ncSendToProcess from $ unsafeCreateUnencodedMessage $ WhereIsReply label mPid
9051020
9061021-- [Unified: Table 14]
9071022ncEffectNamedSend :: String -> Message -> NC ()
9081023ncEffectNamedSend label msg = do
9091024 mPid <- gets (^. registeredHereFor label)
9101025 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
911− forM_ mPid $ \pid -> postMessage pid msg
1026+ forM_ mPid $ \to ->
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
9121030
9131031-- [Issue #DP-20]
9141032ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
915−ncEffectLocalSend node to msg =
1033+ncEffectLocalSend = ncEffectLocalSendAndTrace True
1034+
1035+ncEffectLocalSendAndTrace :: Bool -> LocalNode -> ProcessId -> Message -> NC ()
1036+ncEffectLocalSendAndTrace shouldTrace node to msg =
9161037 liftIO $ withLocalProc node to $ \p -> do
9171038 enqueue (processQueue p) msg
918− trace node (MxReceived to msg)
1039+ when shouldTrace $ trace node (MxReceived to msg)
9191040
9201041-- [Issue #DP-20]
9211042ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
@@ -933,11 +1054,12 @@ ncEffectLocalPortSend from msg = do
9331054 -- If ch is Nothing, the process has given up the read end of
9341055 -- the channel and we simply ignore the incoming message - this
9351056 ch <- deRefWeak chan'
936− forM_ ch $ \chan -> deliverChan msg chan
937− where deliverChan :: forall a . Message -> TQueue a -> IO ()
938− deliverChan (UnencodedMessage _ raw) 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
9391060 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
940− deliverChan (EncodedMessage _ _) _ =
1061+ trace n (MxReceivedPort p $ unsafeCreateUnencodedMessage raw)
1062+ deliverChan _ _ (EncodedMessage _ _) _ =
9411063 -- this will not happen unless someone screws with Primitives.hs
9421064 error "invalid local channel delivery"
9431065
@@ -962,41 +1084,36 @@ ncEffectGetInfo from pid =
9621084 them = (ProcessIdentifier pid)
9631085 in do
9641086 node <- ask
965− mProc <- liftIO $
966− withMVar (localState node) $ return . (^. localProcessWithId lpid)
1087+ mProc <- liftIO $ withValidLocalState node
1088+ $ return . (^. localProcessWithId lpid)
9671089 case mProc of
9681090 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
969− from node (ProcessInfoNone DiedUnknownId)
970− Just _ -> do
971− itsLinks <- gets (^. linksFor them)
972− itsMons <- gets (^. monitorsFor them)
1091+ from (ProcessInfoNone DiedUnknownId)
1092+ Just proc -> do
1093+ itsLinks <- Set.map fst . BiMultiMap.lookupBy1st them <$>
1094+ gets (^. links)
1095+ itsMons <- BiMultiMap.lookupBy1st them <$> gets (^. monitors)
9731096 registered <- gets (^. registeredHere)
1097+ size <- liftIO $ queueSize $ processQueue $ proc
9741098
9751099 let reg = registeredNames registered
9761100 dispatch (isLocal node (ProcessIdentifier from))
9771101 from
978− node
9791102 ProcessInfo {
9801103 infoNode = (processNodeId pid)
9811104 , infoRegisteredNames = reg
982− -- we cannot populate this field
983− , infoMessageQueueLength = Nothing
984− , infoMonitors = Set.toList itsMons
985− , infoLinks = Set.toList itsLinks
1105+ , infoMessageQueueLength = size
1106+ , infoMonitors = Set.toList itsMons
1107+ , infoLinks = Set.toList itsLinks
9861108 }
987− where dispatch :: (Serializable a, Show a)
1109+ where dispatch :: (Serializable a)
9881110 => Bool
9891111 -> ProcessId
990− -> LocalNode
9911112 -> a
9921113 -> NC ()
993− dispatch True dest _ pInfo = postAsMessage dest $ pInfo
994− dispatch False dest node pInfo = do
995− liftIO $ sendMessage node
996− (NodeIdentifier (localNodeId node))
997− (ProcessIdentifier dest)
998− WithImplicitReconnect
999− pInfo
1114+ dispatch True dest pInfo = postAsMessage dest $ pInfo
1115+ dispatch False dest pInfo =
1116+ ncSendToProcess dest $ unsafeCreateUnencodedMessage pInfo
10001117
10011118 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
10021119 then (k:ks)
@@ -1006,17 +1123,17 @@ ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
10061123ncEffectGetNodeStats from _nid = do
10071124 node <- ask
10081125 ncState <- StateT.get
1009− nodeState <- liftIO $ readMVar (localState node)
1010− let localProcesses' = nodeState ^. localProcesses
1011− stats =
1126+ nodeState <- liftIO $ withValidLocalState node return
1127+ let stats =
10121128 NodeStats {
10131129 nodeStatsNode = localNodeId node
10141130 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1015− , nodeStatsMonitors = Map.size $ ncState ^. monitors
1016− , nodeStatsLinks = Map.size $ ncState ^. links
1017− , nodeStatsProcesses = Map.size localProcesses'
1131+ , nodeStatsMonitors = BiMultiMap.size $ ncState ^. monitors
1132+ , nodeStatsLinks = BiMultiMap.size $ ncState ^. links
1133+ , nodeStatsProcesses = Map.size (nodeState ^. localProcesses)
10181134 }
10191135 postAsMessage from stats
1136+
10201137--------------------------------------------------------------------------------
10211138-- Auxiliary --
10221139--------------------------------------------------------------------------------
@@ -1043,14 +1160,10 @@ notifyDied dest src reason mRef = do
10431160 throwException dest $ PortLinkException pid reason
10441161 (False, _, _) ->
10451162 -- The change in sender comes from [Unified: Table 10]
1046− liftIO $ sendBinary node
1047− (NodeIdentifier $ localNodeId node)
1048− (NodeIdentifier $ processNodeId dest)
1049− WithImplicitReconnect
1050− NCMsg
1051− { ctrlMsgSender = NodeIdentifier (localNodeId node)
1052− , ctrlMsgSignal = Died src reason
1053− }
1163+ ncSendToNode (processNodeId dest) $ NCMsg
1164+ { ctrlMsgSender = NodeIdentifier (localNodeId node)
1165+ , ctrlMsgSignal = Died src reason
1166+ }
10541167
10551168-- | [Unified: Table 8]
10561169destNid :: ProcessSignal -> Maybe NodeId
@@ -1062,6 +1175,7 @@ destNid (Spawn _ _) = Nothing
10621175destNid (Register _ _ _ _) = Nothing
10631176destNid (WhereIs _) = Nothing
10641177destNid (NamedSend _ _) = Nothing
1178+destNid (UnreliableSend _ _) = Nothing
10651179-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
10661180-- then when a local process dies the monitoring/linking machinery will take
10671181-- care of notifying remote nodes
@@ -1088,7 +1202,7 @@ unClosure closure = do
10881202isValidLocalIdentifier :: Identifier -> NC Bool
10891203isValidLocalIdentifier ident = do
10901204 node <- ask
1091− liftIO . withMVar (localState node) $ \nSt ->
1205+ liftIO . withValidLocalState node $ \nSt ->
10921206 case ident of
10931207 NodeIdentifier nid ->
10941208 return $ nid == localNodeId node
@@ -1119,6 +1233,9 @@ postMessage pid msg = do
11191233throwException :: Exception e => ProcessId -> e -> NC ()
11201234throwException pid e = do
11211235 node <- ask
1236+ -- throwTo blocks until the exception is received by the target thread.
1237+ -- We cannot easily make it happen asynchronpusly because then 'unlink'
1238+ -- semantics would break.
11221239 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
11231240
11241241withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
@@ -1126,17 +1243,17 @@ withLocalProc node pid p =
11261243 -- By [Unified: table 6, rule missing_process] messages to dead processes
11271244 -- can silently be dropped
11281245 let lpid = processLocalId pid in do
1129− mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1130− forM_ mProc p
1246+ join $ withValidLocalState node $ \vst ->
1247+ return $ forM_ (vst ^. localProcessWithId lpid) p
11311248
11321249--------------------------------------------------------------------------------
11331250-- Accessors --
11341251--------------------------------------------------------------------------------
11351252
1136−links :: Accessor NCState (Map Identifier (Set ProcessId))
1253+links :: Accessor NCState (BiMultiMap Identifier ProcessId ())
11371254links = accessor _links (\ls st -> st { _links = ls })
11381255
1139−monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1256+monitors :: Accessor NCState (BiMultiMap Identifier ProcessId MonitorRef)
11401257monitors = accessor _monitors (\ms st -> st { _monitors = ms })
11411258
11421259registeredHere :: Accessor NCState (Map String ProcessId)
@@ -1145,12 +1262,6 @@ registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry }
11451262registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
11461263registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
11471264
1148−linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1149−linksFor ident = links >>> DAC.mapDefault Set.empty ident
1150−
1151−monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1152−monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1153−
11541265registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
11551266registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
11561267
@@ -1177,10 +1288,12 @@ registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
11771288-- * the notifications for typed channels to that process.
11781289--
11791290-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1180−splitNotif :: Identifier
1181− -> Map Identifier a
1182− -> (Map Identifier a, Map Identifier a)
1183−splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1291+splitNotif :: (Ord a, Ord v)
1292+ => Identifier
1293+ -> BiMultiMap Identifier a v
1294+ -> (Map Identifier (Set (a,v)), BiMultiMap Identifier a v)
1295+splitNotif ident =
1296+ BiMultiMap.partitionWithKeyBy1st (\k !_v -> ident `impliesDeathOf` k)
11841297
11851298--------------------------------------------------------------------------------
11861299-- Auxiliary --
src/Control/Distributed/Process/Serializable.hs+6−14
@@ -1,8 +1,8 @@
11{-# LANGUAGE DeriveDataTypeable #-}
22{-# LANGUAGE UndecidableInstances #-}
33{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
45{-# LANGUAGE GADTs #-}
5−{-# LANGUAGE CPP #-}
66module Control.Distributed.Process.Serializable
77 ( Serializable
88 , encodeFingerprint
@@ -17,25 +17,17 @@ module Control.Distributed.Process.Serializable
1717
1818import Data.Binary (Binary)
1919
20−#if MIN_VERSION_base(4,7,0)
21−import Data.Typeable (Typeable)
22−import Data.Typeable.Internal (TypeRep(TypeRep), typeOf)
23−#else
24−import Data.Typeable (Typeable(..))
25−import Data.Typeable.Internal (TypeRep(TypeRep))
26−#endif
20+import Data.Typeable (Typeable, typeRepFingerprint, typeOf)
2721
2822import Numeric (showHex)
2923import Control.Exception (throw)
3024import GHC.Fingerprint.Type (Fingerprint(..))
3125import Data.ByteString (ByteString)
3226import qualified Data.ByteString as BS
33−import qualified Data.ByteString.Internal as BSI ( unsafeCreate
34− , inlinePerformIO
35− , toForeignPtr
36− )
27+import qualified Data.ByteString.Internal as BSI ( unsafeCreate, toForeignPtr )
3728import Foreign.Storable (pokeByteOff, peekByteOff, sizeOf)
3829import Foreign.ForeignPtr (withForeignPtr)
30+import System.IO.Unsafe (unsafePerformIO)
3931
4032-- | Reification of 'Serializable' (see "Control.Distributed.Process.Closure")
4133data SerializableDict a where
@@ -63,7 +55,7 @@ decodeFingerprint :: ByteString -> Fingerprint
6355decodeFingerprint bs
6456 | BS.length bs /= sizeOfFingerprint =
6557 throw $ userError "decodeFingerprint: Invalid length"
66− | otherwise = BSI.inlinePerformIO $ do
58+ | otherwise = unsafePerformIO $ do
6759 let (fp, offset, _) = BSI.toForeignPtr bs
6860 withForeignPtr fp $ \p -> peekByteOff p offset
6961
@@ -73,7 +65,7 @@ sizeOfFingerprint = sizeOf (undefined :: Fingerprint)
7365
7466-- | The fingerprint of the typeRep of the argument
7567fingerprint :: Typeable a => a -> Fingerprint
76−fingerprint a = let TypeRep fp _ _ = typeOf a in fp
68+fingerprint = typeRepFingerprint . typeOf
7769
7870-- | Show fingerprint (for debugging purposes)
7971showFingerprint :: Fingerprint -> ShowS
src/Control/Distributed/Process/UnsafePrimitives.hs+109−29
@@ -32,7 +32,24 @@
3232-- the /normal/ strategy).
3333--
3434-- Use of the functions in this module can potentially change the runtime
35−-- behaviour of your application. You have been warned!
35+-- behaviour of your application. In addition, messages passed between Cloud
36+-- Haskell processes are written to a tracing infrastructure on the local node,
37+-- to provide improved introspection and debugging facilities for complex actor
38+-- based systems. This module makes no attempt to force evaluation in these
39+-- cases either, thus evaluation problems in passed data structures could not
40+-- only crash your processes, but could also bring down critical internal
41+-- services on which the node relies to function correctly.
42+--
43+-- If you wish to repudiate such issues, you are advised to consider the use
44+-- of NFSerialisable in the distributed-process-extras package, which type
45+-- class brings NFData into scope along with Serializable, such that we can
46+-- force evaluation. Intended for use with modules such as this one, this
47+-- approach guarantees correct evaluatedness in terms of @NFData@. Please note
48+-- however, that we /cannot/ guarantee that an @NFData@ instance will behave the
49+-- same way as a @Binary@ one with regards evaluation, so it is still possible
50+-- to introduce unexpected behaviour by using /unsafe/ primitives in this way.
51+--
52+-- You have been warned!
3653--
3754-- This module is exported so that you can replace the use of Cloud Haskell's
3855-- /safe/ messaging primitives. If you want to use both variants, then you can
@@ -45,6 +62,8 @@ module Control.Distributed.Process.UnsafePrimitives
4562 send
4663 , sendChan
4764 , nsend
65+ , nsendRemote
66+ , usend
4867 , wrapMessage
4968 ) where
5069
@@ -53,9 +72,15 @@ import Control.Distributed.Process.Internal.Messaging
5372 , sendBinary
5473 , sendCtrlMsg
5574 )
56−
75+import Control.Distributed.Process.Management.Internal.Types
76+ ( MxEvent(..)
77+ )
78+import Control.Distributed.Process.Management.Internal.Trace.Types
79+ ( traceEvent
80+ )
5781import Control.Distributed.Process.Internal.Types
5882 ( ProcessId(..)
83+ , NodeId(..)
5984 , LocalNode(..)
6085 , LocalProcess(..)
6186 , Process(..)
@@ -65,6 +90,7 @@ import Control.Distributed.Process.Internal.Types
6590 , ImplicitReconnect(..)
6691 , SendPortId(..)
6792 , Message
93+ , createMessage
6894 , sendPortProcessId
6995 , unsafeCreateUnencodedMessage
7096 )
@@ -75,45 +101,99 @@ import Control.Monad.Reader (ask)
75101
76102-- | Named send to a process in the local registry (asynchronous)
77103nsend :: Serializable a => String -> a -> Process ()
78−nsend label msg =
79− sendCtrlMsg Nothing (NamedSend label (unsafeCreateUnencodedMessage msg))
104+nsend label msg = do
105+ proc <- ask
106+ let us = processId proc
107+ let msg' = wrapMessage msg
108+ -- see [note: tracing]
109+ liftIO $ traceEvent (localEventBus (processNode proc))
110+ (MxSentToName label us msg')
111+ sendCtrlMsg Nothing (NamedSend label msg')
112+
113+-- | Named send to a process in a remote registry (asynchronous)
114+nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
115+nsendRemote nid label msg = do
116+ proc <- ask
117+ let us = processId proc
118+ let node = processNode proc
119+ if localNodeId node == nid
120+ then nsend label msg
121+ else
122+ let lbl = label ++ "@" ++ show nid in do
123+ -- see [note: tracing] NB: this is a remote call to another NC...
124+ liftIO $ traceEvent (localEventBus node)
125+ (MxSentToName lbl us (wrapMessage msg))
126+ sendCtrlMsg (Just nid) (NamedSend label (createMessage msg))
80127
81128-- | Send a message
82129send :: Serializable a => ProcessId -> a -> Process ()
83130send them msg = do
84131 proc <- ask
85132 let node = localNodeId (processNode proc)
86− destNode = (processNodeId them) in do
87− case destNode == node of
88− True -> unsafeSendLocal them msg
89− False -> liftIO $ sendMessage (processNode proc)
90− (ProcessIdentifier (processId proc))
91− (ProcessIdentifier them)
92− NoImplicitReconnect
93− msg
94− where
95− unsafeSendLocal :: (Serializable a) => ProcessId -> a -> Process ()
96− unsafeSendLocal pid msg' =
97− sendCtrlMsg Nothing $ LocalSend pid (unsafeCreateUnencodedMessage msg')
133+ destNode = (processNodeId them)
134+ us = (processId proc)
135+ msg' = wrapMessage msg in do
136+ -- see [note: tracing]
137+ liftIO $ traceEvent (localEventBus (processNode proc))
138+ (MxSent them us msg')
139+ if destNode == node
140+ then sendCtrlMsg Nothing $ LocalSend them msg'
141+ else liftIO $ sendMessage (processNode proc)
142+ (ProcessIdentifier (processId proc))
143+ (ProcessIdentifier them)
144+ NoImplicitReconnect
145+ msg
146+
147+-- | Send a message unreliably.
148+--
149+-- Unlike 'send', this function is insensitive to 'reconnect'. It will
150+-- try to send the message regardless of the history of connection failures
151+-- between the nodes.
152+--
153+-- Message passing with 'usend' is ordered for a given sender and receiver
154+-- if the messages arrive at all.
155+--
156+usend :: Serializable a => ProcessId -> a -> Process ()
157+usend them msg = do
158+ proc <- ask
159+ let there = processNodeId them
160+ let (us, node) = (processId proc, processNode proc)
161+ let msg' = wrapMessage msg
162+ -- see [note: tracing]
163+ liftIO $ traceEvent (localEventBus node) (MxSent them us msg')
164+ if localNodeId (processNode proc) == there
165+ then sendCtrlMsg Nothing $ LocalSend them msg'
166+ else sendCtrlMsg (Just there) $ UnreliableSend (processLocalId them)
167+ (createMessage msg)
168+
169+-- [note: tracing]
170+-- Note that tracing writes to the local node's control channel, and this
171+-- module explicitly specifies to its clients that it does unsafe message
172+-- encoding. The same is true for the messages it puts onto the Management
173+-- event bus, however we do *not* want unevaluated thunks hitting the event
174+-- bus control thread. Hence the word /Unsafe/ in this module's name!
175+--
98176
99177-- | Send a message on a typed channel
100178sendChan :: Serializable a => SendPort a -> a -> Process ()
101179sendChan (SendPort cid) msg = do
102180 proc <- ask
103− let node = localNodeId (processNode proc)
104− destNode = processNodeId (sendPortProcessId cid) in do
105− case destNode == node of
106− True -> unsafeSendChanLocal cid msg
107− False -> do
108− liftIO $ sendBinary (processNode proc)
109− (ProcessIdentifier (processId proc))
110− (SendPortIdentifier cid)
111− NoImplicitReconnect
112− msg
181+ let node = processNode proc
182+ pid = processId proc
183+ us = localNodeId node
184+ them = processNodeId (sendPortProcessId cid)
185+ msg' = wrapMessage msg in do
186+ liftIO $ traceEvent (localEventBus node) (MxSentToPort pid cid msg')
187+ if them == us
188+ then unsafeSendChanLocal cid msg' -- NB: we wrap to P.Message !!!
189+ else liftIO $ sendBinary node
190+ (ProcessIdentifier pid)
191+ (SendPortIdentifier cid)
192+ NoImplicitReconnect
193+ msg
113194 where
114− unsafeSendChanLocal :: (Serializable a) => SendPortId -> a -> Process ()
115− unsafeSendChanLocal spId msg' =
116− sendCtrlMsg Nothing $ LocalPortSend spId (unsafeCreateUnencodedMessage msg')
195+ unsafeSendChanLocal :: SendPortId -> Message -> Process ()
196+ unsafeSendChanLocal p m = sendCtrlMsg Nothing $ LocalPortSend p m
117197
118198-- | Create an unencoded @Message@ for any @Serializable@ type.
119199wrapMessage :: Serializable a => a -> Message
stack-ghc-7.10.3.yamlnew file+21
@@ -0,0 +1,21 @@
1+resolver: nightly-2016-03-08
2+
3+packages:
4+- '.'
5+- distributed-process-tests/
6+- location:
7+ git: https://github.com/haskell-distributed/network-transport-tcp.git
8+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
9+ extra-dep: true
10+
11+extra-deps:
12+- rank1dynamic-0.4.0
13+- distributed-static-0.3.8
14+- network-transport-0.5.2
15+- network-transport-inmemory-0.5.2
16+- rematch-0.2.0.0
17+- exceptions-0.8.2.1 # test dependency
18+
19+flags:
20+ distributed-process-tests:
21+ tcp: true
stack-ghc-8.0.2.yamlnew file+23
@@ -0,0 +1,23 @@
1+resolver: lts-9.21
2+# added -location for network-transport-tcp-0.6.0
3+# since c7fd79 broke the client facing API for createTransport :/
4+
5+packages:
6+- '.'
7+- distributed-process-tests/
8+- location:
9+ git: https://github.com/haskell-distributed/network-transport-tcp.git
10+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
11+ extra-dep: true
12+
13+extra-deps:
14+- rank1dynamic-0.4.0
15+- distributed-static-0.3.8
16+- network-transport-0.5.2
17+- network-transport-inmemory-0.5.2
18+- rematch-0.2.0.0
19+# - exceptions-0.8.2.1 # test dependency
20+
21+flags:
22+ distributed-process-tests:
23+ tcp: true
stack-ghc-8.2.2.yamlnew file+21
@@ -0,0 +1,21 @@
1+resolver: lts-11.22
2+
3+packages:
4+- '.'
5+- distributed-process-tests/
6+- location:
7+ git: https://github.com/haskell-distributed/network-transport-tcp.git
8+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
9+ extra-dep: true
10+
11+extra-deps:
12+- rank1dynamic-0.4.0
13+- distributed-static-0.3.8
14+- network-transport-0.5.2
15+- network-transport-inmemory-0.5.2
16+- rematch-0.2.0.0
17+# - exceptions-0.8.2.1 # test dependency
18+
19+flags:
20+ distributed-process-tests:
21+ tcp: true
stack-ghc-8.4.4.yamlnew file+20
@@ -0,0 +1,20 @@
1+resolver: lts-12.18
2+
3+packages:
4+- '.'
5+- distributed-process-tests/
6+- location:
7+ git: https://github.com/haskell-distributed/network-transport-tcp.git
8+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
9+ extra-dep: true
10+
11+extra-deps:
12+- rank1dynamic-0.4.0
13+- distributed-static-0.3.8
14+- network-transport-0.5.2
15+- network-transport-inmemory-0.5.2
16+- rematch-0.2.0.0
17+
18+flags:
19+ distributed-process-tests:
20+ tcp: true
stack-nightly.yamlnew file+26
@@ -0,0 +1,26 @@
1+resolver: nightly
2+
3+packages:
4+- '.'
5+- distributed-process-tests/
6+- location:
7+ git: https://github.com/haskell-distributed/distributed-static.git
8+ commit: c51e6aadd3a44c044e96a8e986c11d3a94b45b11
9+ extra-dep: true
10+- location:
11+ git: https://github.com/haskell-distributed/network-transport-tcp.git
12+ commit: d58250e2388b86a1865ac4139373cbdc106e72e1
13+ extra-dep: true
14+- location:
15+ git: https://github.com/haskell-distributed/network-transport-inmemory.git
16+ commit: 0ce97924f15a8c1570b2355151834305c9bd2a28
17+ extra-dep: true
18+
19+extra-deps:
20+- rank1dynamic-0.4.0
21+- network-transport-0.5.2
22+- rematch-0.2.0.0
23+
24+flags:
25+ distributed-process-tests:
26+ tcp: true
stack.yamlnew file+22
@@ -0,0 +1,22 @@
1+resolver: lts-12.18 # Use GHC 8.4.4
2+
3+# TODO: we can enable nightly-2018-11-11 once PR #319 is merged
4+
5+packages:
6+- '.'
7+- distributed-process-tests/
8+- location:
9+ git: https://github.com/haskell-distributed/network-transport-tcp.git
10+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
11+ extra-dep: true
12+
13+extra-deps:
14+- rank1dynamic-0.4.0
15+- distributed-static-0.3.8
16+- network-transport-0.5.2
17+- network-transport-inmemory-0.5.2
18+- rematch-0.2.0.0
19+
20+flags:
21+ distributed-process-tests:
22+ tcp: true