git.iko.soy/distributed-process
on master help sign in
.gitignore+3−13
since splitsnapshots
unifiedsplit
@@ -1,14 +1,4 @@
1−tags
2−TAGS
3dist/
1dist/
4−\#*#
5−*~
6−*.aux
7−*.hp
8−*.hi
9−*.o
10−*.prof
11−*.ps
12−*.swp
13−cabal-dev
2+.cabal-sandbox
14−.DS_Store
3+cabal.sandbox.config
4+.stack*
.travis.yml+42−13
@@ -1,14 +1,43 @@
1−language: haskell
1+language: c
2+
3+sudo: false
4+
5+matrix:
6+ include:
7+ - env: ARGS="--stack-yaml stack-ghc-7.10.3.yaml"
8+ addons: {apt: {packages: [libgmp-dev]}}
9+ - env: ARGS="--stack-yaml stack-ghc-8.2.2.yaml"
10+ addons: {apt: {packages: [libgmp-dev]}}
11+ - env: ARGS="--stack-yaml stack-ghc-8.4.4.yaml"
12+ addons: {apt: {packages: [libgmp-dev]}}
13+ - env: ARGS="--stack-yaml stack-nightly.yaml --resolver nightly"
14+ addons: {apt: {packages: [libgmp-dev]}}
15+
16+cache:
17+ directories:
18+ - $HOME/.stack
19+
20+before_install:
21+- export PATH=$HOME/.local/bin:$PATH;
22+ mkdir -p ~/.local/bin;
23+ travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack';
24+ stack --version
25+
26+install:
27+- stack $ARGS setup --no-terminal;
28+ stack $ARGS build --no-terminal --only-snapshot --test --no-run-tests;
29+
2−script: "make ci"
30+script:
31+- export ARG='--test-arguments'
32+ export TEST_PACKAGE="distributed-process-tests:"
33+- stack ${ARGS} test $ARG='--plain -t "!Flaky"' ${TEST_PACKAGE}TestCHInMemory
34+- stack ${ARGS} test $ARG='--plain -t "!Flaky"' ${TEST_PACKAGE}TestCHInTCP
35+- stack ${ARGS} test $ARG='--plain -t "!SpawnReconnect"' ${TEST_PACKAGE}TestClosure
36+- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestStats
37+- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestMxInMemory
38+- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestMxInTCP
39+- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestTracingInMemory
40+
3notifications:
41notifications:
4− email:
42+ slack:
5− recipients:
43+ secure: LbqbNjK1KiHNiQo/i/Za4vkMD9l9mhmN+PWYFBQkNvGFWffSRayRKFrTqt4znV2p7h4dY1XZFoC7T5RemBVQLq2ppZAUWxkITeu1OUlnmVLDfJLeVYGTR/fn90nP6Y5ITE7T3A07nKmWaRbKpIgFBAOjzgqaM0csscDx3z0WW18=
6− - cloud.haskell@gmail.com
7− irc:
8− channels:
9− - "irc.freenode.org#haskell-distributed"
10− use_notice: true
11−branches:
12− only:
13− - master
14− - development
CONTRIBUTING.mdnew file+1
@@ -0,0 +1 @@
1+See https://github.com/haskell-distributed/cloud-haskell/blob/master/CONTRIBUTING.md.
ChangeLog+138
@@ -1,3 +1,141 @@
1+[Unrealeased]
2+
3+*
4+
5+2018-06-12 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.4
6+
7+* Added support for exceptions >= 0.10
8+
9+2017-08-31 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.3
10+
11+* Drop support for ghc-7.8.* and earlier.
12+
13+2017-08-31 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.2
14+
15+* Fixed build errors with ghc-8.2.1.
16+
17+2017-08-22 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.1
18+
19+* Relax upper bounds in dependencies to build with ghc-8.2.1.
20+
21+2017-08-21 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.0
22+
23+* Change type of message sent by `say` from a 3-tuple to a proper
24+type (`SayMessage`) with a proper `UTCTime`. (#291)
25+* Expose the MonitorRef in the withMonitor call.
26+* Have unmonitor remove the monitor message in the inbox. (#268)
27+* Remove Mx Data Tables. This API isn't used, is easy to replace with various
28+other packages. (#276)
29+* Add Ord instance to SpawnRef.
30+
31+2016-10-13 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.6
32+
33+* Remove monitors from remote nodes when a process dies. (#295)
34+
35+2016-10-12 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.5
36+
37+* Use only one connection to communicate between NCs. (#296, #297)
38+* Improve documentation of CQueue.
39+* Implement bidirectional multimaps for links and monitors. (#293, #294)
40+* Fix various warnings. (#292)
41+* Fix some of the intermittent failures in tests.
42+* Improve error messages when node controllers receive invalid requests.
43+(#290)
44+
45+2016-06-09 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.4
46+
47+* Fixup build errors.
48+
49+2016-06-09 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.3
50+
51+* Relax template-haskell upper bound.
52+
53+2016-06-09 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.2
54+
55+* Provide compatibility with ghc-8.0.1
56+* Remove dependency on ghc-prim.
57+* Don't throw exceptions asynchronously.
58+* Bump upper bounds of dependencies.
59+* Fix exception handling in callLocal.
60+* Have spawnLocal inherit the masking state of the caller.
61+* Have nsend send unencoded messages to local processes.
62+
63+2016-03-03 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.1
64+
65+* Implement MonadCatch, MonadThrow, MonadMask for Process.
66+
67+2016-02-18 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.0
68+
69+* Have nsendRemote skip the transport for local communication.
70+* Unsafe primitives for usend and nsendRemote.
71+* Stop using the transport for local communication.
72+* Skip the transport for whereisRemoteAsync and registerRemoteAsync.
73+* Have nsendRemote skip the transport for local communication.
74+* Have runProcess forward exceptions.
75+* Reimport distributed-process-tests. d-p and d-p-test now can be kept in
76+sync.
77+* Add a stack.yaml file for building tests and d-p all at once conveniently.
78+* Implement unreliable forward (uforward).
79+* Add Functor instance for Match data type
80+* Have `spawnAsync` not use the transport in the local case.
81+* Fix monitor race in 'call'.
82+* Add compatibility with ghc-7.10: support new typeable, loosen deps, write
83+proper NFData instances, support new TH.
84+* Kill processes on a local node upon closeLocalNode.
85+* Fix getNodeStats function, see DP-97
86+* Return size of the queue in ProcessInfo.
87+* Implement unreliable send (usend).
88+* Implement MonadFix instance for Process.
89+* Introduce callLocal primitive.
90+* Prevent message loss due to timeouts in CQueue.
91+* More informative ProcessRegistrationException. Now includes the identifier
92+of the process that owns the name, if any.
93+* Avoid message loop between threads when tracing received messages.
94+
95+2015-06-15 Facundo Domínguez <facundo.dominguez@tweag.io> 0.5.5
96+
97+* Fix dependencies.
98+* Add compatibility with GHC-7.10.
99+* Fix various race conditions (DP-99, DP-103).
100+
101+2014-12-09 Tim Watson <watson.timothy@gmail.com> 0.5.2
102+
103+* Fix docstring for `register`
104+* Added Data instance to ProcessId, LocalProcessId and NodeId
105+* Add static serialiation dictionary for 'Static', for completeness
106+* Add Closure static serialization dictionary
107+* Replacement for modifyMVarMasked for GHC <= 7.4
108+* Document the use of built-in trace flags
109+* Make forkProcess exception-safe
110+* Make -Wall clean
111+
112+2014-08-13 Tim Watson <watson.timothy@gmail.com> 0.5.1
113+
114+* Fix cabal docs (thanks Markus Barenhoff)
115+* Expose lifted version of Control.Exception.mask_ (thanks Alexander Vershilov)
116+
117+2014-05-30 Tim Watson <watson.timothy@gmail.com> 0.5.0
118+
119+* Dependency on STM implicitly changed from 1.3 to 1.4, but was not reflected in the cabal file
120+* Race condition in local monitoring when using call
121+* mask now works correctly if unmask is called by another process
122+* Improve efficiency of local message passing
123+* nsend uses local communication channels
124+* Link Node Controller and Network Listener
125+* Label spawned processes using labelThread
126+* Relaxed upper bound on syb in the cabal manifest
127+* Bump binary version to include 0.7.*
128+* Exposed process info
129+* Exposed node statistics
130+* Moved tests to https://github.com/haskell-distributed/distributed-process-tests
131+* Added "polymorphic expect"
132+* Exposed Message type and broaden scope of polymorphic expect
133+* Added Management API (for working with internal/system events)
134+* Tracing can no longer be disabled
135+* We now report node statistics for monitoring/management
136+* Node.runProcess now propagates exceptions to its caller
137+* Added simple micro benchmarks
138+
12013-01-27 Tim Watson <watson.timothy@gmail.com> 0.4.2
1392013-01-27 Tim Watson <watson.timothy@gmail.com> 0.4.2
2
140
3* Improved exception handling for deferred type checked exit reasons
141* Improved exception handling for deferred type checked exit reasons
LICENSE+1
@@ -1,4 +1,5 @@
1Copyright Well-Typed LLP, 2011-2012
1Copyright Well-Typed LLP, 2011-2012
2+Copyright Tweag I/O Limited, 2015
2
3
3All rights reserved.
4All rights reserved.
4
5
Makefiledeleted−39
@@ -1,39 +0,0 @@
1−# CI build
2−
3−GHC ?= $(shell which ghc)
4−CABAL ?= $(shell which cabal)
5−CABAL_DEV ?= $(shell which cabal-dev)
6−
7−BASE_GIT := git://github.com/haskell-distributed
8−REPOS=$(shell cat REPOS | sed '/^$$/d')
9−
10−.PHONY: all
11−all: dev-install
12−
13−.PHONY: dev-install
14−ifneq (,$(CABAL_DEV))
15−dev-install:
16− $(CABAL_DEV) install
17−else
18−dev-install:
19− $(error install cabal-dev to proceed)
20−endif
21−
22−.PHONY: ci
23−ci: travis-install travis-test
24−
25−.PHONY: travis-install
26−travis-install: $(REPOS)
27− $(CABAL) install QuickCheck-2.6 --force-reinstalls
28− $(CABAL) install rematch --force-reinstalls
29− $(CABAL) install --with-ghc=$(GHC) $(REPOS) --force-reinstalls
30− $(CABAL) install
31−
32−.PHONY: travis-test
33−travis-test:
34− $(CABAL) configure --enable-tests
35− $(CABAL) build
36− $(CABAL) test --show-details=always
37−
38−$(REPOS):
39− git clone $(BASE_GIT)/$@.git
README.md+7−10
@@ -1,19 +1,16 @@
1−### distributed-process
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)
2
4
3This repository is part of Cloud Haskell.
5This repository is part of Cloud Haskell.
4
6
5See http://haskell-distributed.github.com for documentation, user guides,
7See http://haskell-distributed.github.com for documentation, user guides,
6tutorials and assistance.
8tutorials and assistance.
7
9
8−### Getting Help / Raising Issues
10+## Getting Help / Raising Issues
9
11
10−Please visit our [bug tracker](http://cloud-haskell.atlassian.net) to submit
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.
11−issues. Anyone can browse, although you'll need to provide an email address
12−and create an account in order to submit new issues.
13
13
14−If you'd like to talk to a human, please contact us at the parallel-haskell
14+## License
15−mailing list in the first instance - parallel-haskell@googlegroups.com.
16
15
17−### License
18−
19−distributed-process is made available under a BSD-3 license.
16+This package is made available under a 3-clause BSD-style license.
REPOSdeleted−4
@@ -1,4 +0,0 @@
1−rank1dynamic
2−distributed-static
3−network-transport
4−network-transport-tcp
benchmarks/Channels.hs+6−4
@@ -4,7 +4,7 @@ import Control.Monad
4import Control.Applicative
4import Control.Applicative
5import Control.Distributed.Process
5import Control.Distributed.Process
6import Control.Distributed.Process.Node
6import Control.Distributed.Process.Node
7−import Network.Transport.TCP (createTransport, defaultTCPParameters)
7+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
8import Data.Binary (encode, decode)
8import Data.Binary (encode, decode)
9import qualified Data.ByteString.Lazy as BSL
9import qualified Data.ByteString.Lazy as BSL
10
10
@@ -36,6 +36,8 @@ initialProcess "CLIENT" = do
36main :: IO ()
36main :: IO ()
37main = do
37main = do
38 [role, host, port] <- getArgs
38 [role, host, port] <- getArgs
39− Right transport <- createTransport host port defaultTCPParameters
39+ trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
40+ case trans of
40− node <- newLocalNode transport initRemoteTable
41+ Right transport -> do node <- newLocalNode transport initRemoteTable
41− runProcess node $ initialProcess role
42+ runProcess node $ initialProcess role
43+ Left other -> error $ show other
benchmarks/Latency.hs+7−4
@@ -3,7 +3,7 @@ import Control.Monad
3import Control.Applicative
3import Control.Applicative
4import Control.Distributed.Process
4import Control.Distributed.Process
5import Control.Distributed.Process.Node
5import Control.Distributed.Process.Node
6−import Network.Transport.TCP (createTransport, defaultTCPParameters)
6+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
7import Data.Binary (encode, decode)
7import Data.Binary (encode, decode)
8import qualified Data.ByteString.Lazy as BSL
8import qualified Data.ByteString.Lazy as BSL
9
9
@@ -31,6 +31,9 @@ initialProcess "CLIENT" = do
31main :: IO ()
31main :: IO ()
32main = do
32main = do
33 [role, host, port] <- getArgs
33 [role, host, port] <- getArgs
34+ trans <- createTransport
34− Right transport <- createTransport host port defaultTCPParameters
35+ (defaultTCPAddr host port) defaultTCPParameters
36+ case trans of
35− node <- newLocalNode transport initRemoteTable
37+ Right transport -> do node <- newLocalNode transport initRemoteTable
36− runProcess node $ initialProcess role
38+ runProcess node $ initialProcess role
39+ Left other -> error $ show other
benchmarks/ProcessRing.hs+8−4
@@ -4,11 +4,15 @@ To run the benchmarks, select a value for the ring size (sz) and
4the number of times to send a message around the ring
4the number of times to send a message around the ring
5
5
6-}
6-}
7+
8+{-# LANGUAGE BangPatterns #-}
9+{-# LANGUAGE ScopedTypeVariables #-}
10+
7import Control.Monad
11import Control.Monad
8import Control.Distributed.Process hiding (catch)
12import Control.Distributed.Process hiding (catch)
9import Control.Distributed.Process.Node
13import Control.Distributed.Process.Node
10import Control.Exception (catch, SomeException)
14import Control.Exception (catch, SomeException)
11−import Network.Transport.TCP (createTransport, defaultTCPParameters)
15+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
12import System.Environment
16import System.Environment
13import System.Console.GetOpt
17import System.Console.GetOpt
14
18
@@ -81,7 +85,7 @@ options =
81 (NoArg (\opts -> opts { optUnsafe = True }))
85 (NoArg (\opts -> opts { optUnsafe = True }))
82 "use 'unsafeSend' (ignored with -f) - default = False"
86 "use 'unsafeSend' (ignored with -f) - default = False"
83 , Option ['p'] ["parallel"]
87 , Option ['p'] ["parallel"]
84− (NoArg (\opts -> opts { optForward = True }))
88+ (NoArg (\opts -> opts { optParallel = True }))
85 "send in parallel and consume sequentially - default = False"
89 "send in parallel and consume sequentially - default = False"
86 ]
90 ]
87
91
@@ -106,8 +110,8 @@ main = do
106 argv <- getArgs
110 argv <- getArgs
107 (opt, _) <- parseArgv argv
111 (opt, _) <- parseArgv argv
108 putStrLn $ "options: " ++ (show opt)
112 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
110 node <- newLocalNode transport initRemoteTable
115 node <- newLocalNode transport initRemoteTable
111 catch (void $ runProcess node $ initialProcess opt)
116 catch (void $ runProcess node $ initialProcess opt)
112 (\(e :: SomeException) -> putStrLn $ "ERROR: " ++ (show e))
117 (\(e :: SomeException) -> putStrLn $ "ERROR: " ++ (show e))
113−
benchmarks/Spawns.hs+8−4
@@ -1,3 +1,5 @@
1+{-# LANGUAGE BangPatterns #-}
2+
1-- | Like Throughput, but send every ping from a different process
3-- | Like Throughput, but send every ping from a different process
2-- (i.e., require a lightweight connection per ping)
4-- (i.e., require a lightweight connection per ping)
3import System.Environment
5import System.Environment
@@ -5,7 +7,7 @@ import Control.Monad
5import Control.Applicative
7import Control.Applicative
6import Control.Distributed.Process
8import Control.Distributed.Process
7import Control.Distributed.Process.Node
9import Control.Distributed.Process.Node
8−import Network.Transport.TCP (createTransport, defaultTCPParameters)
10+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
9import Data.Binary (encode, decode)
11import Data.Binary (encode, decode)
10import qualified Data.ByteString.Lazy as BSL
12import qualified Data.ByteString.Lazy as BSL
11
13
@@ -40,6 +42,8 @@ initialProcess "CLIENT" = do
40main :: IO ()
42main :: IO ()
41main = do
43main = do
42 [role, host, port] <- getArgs
44 [role, host, port] <- getArgs
43− Right transport <- createTransport host port defaultTCPParameters
45+ trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
46+ case trans of
44− node <- newLocalNode transport initRemoteTable
47+ Right transport -> do node <- newLocalNode transport initRemoteTable
45− runProcess node $ initialProcess role
48+ runProcess node $ initialProcess role
49+ Left other -> error $ show other
benchmarks/Throughput.hs+8−4
@@ -1,10 +1,12 @@
1+{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2{-# LANGUAGE DeriveDataTypeable #-}
3+
2import System.Environment
4import System.Environment
3import Control.Monad
5import Control.Monad
4import Control.Applicative
6import Control.Applicative
5import Control.Distributed.Process
7import Control.Distributed.Process
6import Control.Distributed.Process.Node
8import Control.Distributed.Process.Node
7−import Network.Transport.TCP (createTransport, defaultTCPParameters)
9+import Network.Transport.TCP (createTransport, defaultTCPParameters, defaultTCPAddr)
8import Data.Binary
10import Data.Binary
9import qualified Data.ByteString.Lazy as BSL
11import qualified Data.ByteString.Lazy as BSL
10import Data.Typeable
12import Data.Typeable
@@ -65,6 +67,8 @@ initialProcess "CLIENT" = do
65main :: IO ()
67main :: IO ()
66main = do
68main = do
67 [role, host, port] <- getArgs
69 [role, host, port] <- getArgs
68− Right transport <- createTransport host port defaultTCPParameters
70+ trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
71+ case trans of
69− node <- newLocalNode transport initRemoteTable
72+ Right transport -> do node <- newLocalNode transport initRemoteTable
70− runProcess node $ initialProcess role
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+ ]
tests/TestReceive.hs → distributed-process-tests/src/Control/Distributed/Process/Tests/Receive.hsrenamed+22−36
@@ -1,24 +1,21 @@
1−{-# LANGUAGE TemplateHaskell, DeriveDataTypeable #-}
1+{-# LANGUAGE TemplateHaskell #-}
2{-# OPTIONS_GHC -Wall #-}
2{-# OPTIONS_GHC -Wall #-}
3−import Control.Distributed.Process
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+
4−import Control.Distributed.Process.Closure
7+module Control.Distributed.Process.Tests.Receive (tests) where
5−import Control.Distributed.Process.Node
8+
9+import Network.Transport.Test (TestTransport(..))
6
10
7import Network.Transport (Transport)
11import Network.Transport (Transport)
8−import Network.Transport.TCP
12+import Control.Distributed.Process
9− ( createTransportExposeInternals
13+import Control.Distributed.Process.Node
10− , defaultTCPParameters
11− , TransportInternals(socketBetween)
12− )
13
14
14import Control.Monad
15import Control.Monad
15−import Text.Printf
16−import Data.DeriveTH
17−import Data.Binary
18−import Data.Typeable
19
16
20import Test.HUnit (Assertion, (@?=))
17import Test.HUnit (Assertion, (@?=))
21−import Test.Framework (Test, defaultMain)
18+import Test.Framework (Test)
22import Test.Framework.Providers.HUnit (testCase)
19import Test.Framework.Providers.HUnit (testCase)
23
20
24-- Tests:
21-- Tests:
@@ -45,7 +42,7 @@ recTest2 :: ReceivePort ()
45 -> SendPort String
42 -> SendPort String
46 -> ReceivePort String -> ReceivePort String
43 -> ReceivePort String -> ReceivePort String
47 -> Process ()
44 -> Process ()
48−recTest2 wait sync r1 r2 = do
45+recTest2 wait sync r1 _ = do
49 forever $ do
46 forever $ do
50 receiveChan wait
47 receiveChan wait
51 r <- receiveWait
48 r <- receiveWait
@@ -58,7 +55,7 @@ recTest3 :: ReceivePort ()
58 -> SendPort String
55 -> SendPort String
59 -> ReceivePort String -> ReceivePort String
56 -> ReceivePort String -> ReceivePort String
60 -> Process ()
57 -> Process ()
61−recTest3 wait sync r1 r2 = do
58+recTest3 wait sync r1 _ = do
62 forever $ do
59 forever $ do
63 receiveChan wait
60 receiveChan wait
64 r <- receiveWait
61 r <- receiveWait
@@ -71,7 +68,7 @@ recTest4 :: ReceivePort ()
71 -> SendPort String
68 -> SendPort String
72 -> ReceivePort String -> ReceivePort String
69 -> ReceivePort String -> ReceivePort String
73 -> Process ()
70 -> Process ()
74−recTest4 wait sync r1 r2 = do
71+recTest4 wait sync r1 _ = do
75 forever $ do
72 forever $ do
76 receiveChan wait
73 receiveChan wait
77 r <- receiveWait
74 r <- receiveWait
@@ -81,17 +78,15 @@ recTest4 wait sync r1 r2 = do
81 ]
78 ]
82 sendChan sync r
79 sendChan sync r
83
80
84−
85master :: Process ()
81master :: Process ()
86master = do
82master = do
87−
88 (waits,waitr) <- newChan
83 (waits,waitr) <- newChan
89 (syncs,syncr) <- newChan
84 (syncs,syncr) <- newChan
90− let go expect = do
85+ let go expected = do
91 sendChan waits ()
86 sendChan waits ()
92 r <- receiveChan syncr
87 r <- receiveChan syncr
93− liftIO $ print (r,expect, r == expect)
88+ liftIO $ print (r, expected, r == expected)
94− liftIO $ r @?= expect
89+ liftIO $ r @?= expected
95
90
96 liftIO $ putStrLn "---- Test 1 ----"
91 liftIO $ putStrLn "---- Test 1 ----"
97 (s1,r1) <- newChan
92 (s1,r1) <- newChan
@@ -147,22 +142,13 @@ master = do
147
142
148 terminate
143 terminate
149
144
150−
151testReceive :: Transport -> RemoteTable -> Assertion
145testReceive :: Transport -> RemoteTable -> Assertion
152testReceive transport rtable = do
146testReceive transport rtable = do
153 node <- newLocalNode transport rtable
147 node <- newLocalNode transport rtable
154 runProcess node $ master
148 runProcess node $ master
155
149
156−tests :: (Transport, TransportInternals) -> RemoteTable -> [Test]
150+tests :: TestTransport -> IO [Test]
157−tests (transport, transportInternals) rtable = [
151+tests TestTransport{..} = do
158− testCase "testReceive" (testReceive transport rtable)
159− ]
160−
161−-- NB. test doesn't work, because failure exceptions don't get
162−-- propagated. The test always claims to succeed, even if it failed.
163−
164−main :: IO ()
165−main = do
166− Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
167− let rtable = initRemoteTable
152+ let rtable = initRemoteTable
168− defaultMain (tests transport rtable)
153+ return
154+ [ testCase "testReceive" (testReceive testTransport rtable) ]
tests/TestStats.hs → distributed-process-tests/src/Control/Distributed/Process/Tests/Stats.hsrenamed+26−61
@@ -1,6 +1,9 @@
1{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2{-# OPTIONS_GHC -fno-warn-orphans #-}
2{-# OPTIONS_GHC -fno-warn-orphans #-}
3−module Main where
3+module Control.Distributed.Process.Tests.Stats (tests) where
4+
5+import Control.Distributed.Process.Tests.Internal.Utils
6+import Network.Transport.Test (TestTransport(..))
4
7
5import Control.Concurrent.MVar
8import Control.Concurrent.MVar
6 ( MVar
9 ( MVar
@@ -10,52 +13,23 @@ import Control.Concurrent.MVar
10 )
13 )
11import Control.Distributed.Process
14import Control.Distributed.Process
12import Control.Distributed.Process.Node
15import Control.Distributed.Process.Node
13− ( forkProcess
14− , newLocalNode
15− , initRemoteTable
16− , closeLocalNode
17− , LocalNode)
18−import Data.Binary()
16+import Data.Binary ()
19−import Data.Typeable()
17+import Data.Typeable ()
20−import Network.Transport.TCP
18+
21#if ! MIN_VERSION_base(4,6,0)
19#if ! MIN_VERSION_base(4,6,0)
22import Prelude hiding (catch)
20import Prelude hiding (catch)
23#endif
21#endif
22+
24import Test.Framework
23import Test.Framework
25 ( Test
24 ( Test
26− , defaultMain
27 , testGroup
25 , testGroup
28 )
26 )
29import Test.HUnit (Assertion)
27import Test.HUnit (Assertion)
30−import Test.HUnit.Base (assertBool)
31import Test.Framework.Providers.HUnit (testCase)
28import Test.Framework.Providers.HUnit (testCase)
32
29
33−-- these utilities have been cribbed from distributed-process-platform
34−-- we should really find a way to share them...
35−
36−-- | A mutable cell containing a test result.
37−type TestResult a = MVar a
38−
39−delayedAssertion :: (Eq a) => String -> LocalNode -> a ->
40− (TestResult a -> Process ()) -> Assertion
41−delayedAssertion note localNode expected testProc = do
42− result <- newEmptyMVar
43− _ <- forkProcess localNode $ testProc result
44− assertComplete note result expected
45−
46−assertComplete :: (Eq a) => String -> MVar a -> a -> IO ()
47−assertComplete msg mv a = do
48− b <- takeMVar mv
49− assertBool msg (a == b)
50−
51−stash :: TestResult a -> a -> Process ()
52−stash mvar x = liftIO $ putMVar mvar x
53−
54−------
55−
56testLocalDeadProcessInfo :: TestResult (Maybe ProcessInfo) -> Process ()
30testLocalDeadProcessInfo :: TestResult (Maybe ProcessInfo) -> Process ()
57testLocalDeadProcessInfo result = do
31testLocalDeadProcessInfo result = do
58− pid <- spawnLocal $ do "finish" <- expect; return ()
32+ pid <- spawnLocal $ do (_ :: String) <- expect; return ()
59 mref <- monitor pid
33 mref <- monitor pid
60 send pid "finish"
34 send pid "finish"
61 _ <- receiveWait [
35 _ <- receiveWait [
@@ -78,8 +52,10 @@ testLocalLiveProcessInfo result = do
78 link self
52 link self
79 mRef <- monitor self
53 mRef <- monitor self
80 stash mon mRef
54 stash mon mRef
81− "die" <- expect
55+ res <- expect
56+ case res of
82− return ()
57+ "die" -> return ()
58+ _ -> die $ "unexpected message received: " ++ res
83
59
84 monRef <- liftIO $ takeMVar mon
60 monRef <- liftIO $ takeMVar mon
85
61
@@ -99,8 +75,8 @@ testLocalLiveProcessInfo result = do
99-- infoMessageQueueLength pInfo == Just 4 &&
75-- infoMessageQueueLength pInfo == Just 4 &&
100 infoRegisteredNames pInfo == ["foobar"]
76 infoRegisteredNames pInfo == ["foobar"]
101
77
102−testRemoteLiveProcessInfo :: LocalNode -> Assertion
78+testRemoteLiveProcessInfo :: TestTransport -> LocalNode -> Assertion
103−testRemoteLiveProcessInfo node1 = do
79+testRemoteLiveProcessInfo TestTransport{..} node1 = do
104 serverAddr <- liftIO $ newEmptyMVar :: IO (MVar ProcessId)
80 serverAddr <- liftIO $ newEmptyMVar :: IO (MVar ProcessId)
105 liftIO $ launchRemote serverAddr
81 liftIO $ launchRemote serverAddr
106 serverPid <- liftIO $ takeMVar serverAddr
82 serverPid <- liftIO $ takeMVar serverAddr
@@ -110,21 +86,23 @@ testRemoteLiveProcessInfo node1 = do
110 -- our send op shouldn't overtake link or monitor requests AFAICT
86 -- our send op shouldn't overtake link or monitor requests AFAICT
111 -- so a little table tennis should get us synchronised properly
87 -- so a little table tennis should get us synchronised properly
112 send serverPid (self, "ping")
88 send serverPid (self, "ping")
113− "pong" <- expect
89+ pong <- expect
114 pInfo <- getProcessInfo serverPid
90 pInfo <- getProcessInfo serverPid
115− stash result $ pInfo /= Nothing
91+ stash result $ pong == "pong" && pInfo /= Nothing
116 where
92 where
117 launchRemote :: MVar ProcessId -> IO ()
93 launchRemote :: MVar ProcessId -> IO ()
118 launchRemote locMV = do
94 launchRemote locMV = do
119− node2 <- liftIO $ mkNode "8082"
95+ node2 <- liftIO $ newLocalNode testTransport initRemoteTable
120 _ <- liftIO $ forkProcess node2 $ do
96 _ <- liftIO $ forkProcess node2 $ do
121 self <- getSelfPid
97 self <- getSelfPid
122 liftIO $ putMVar locMV self
98 liftIO $ putMVar locMV self
123 _ <- receiveWait [
99 _ <- receiveWait [
124 match (\(pid, "ping") -> send pid "pong")
100 match (\(pid, "ping") -> send pid "pong")
125 ]
101 ]
126− "stop" <- expect
102+ res <- expect
103+ case res of
127− return ()
104+ "stop" -> return ()
105+ _ -> die $ "unexpected message received: " ++ res
128 return ()
106 return ()
129
107
130 withActiveRemote :: LocalNode
108 withActiveRemote :: LocalNode
@@ -133,8 +111,9 @@ testRemoteLiveProcessInfo node1 = do
133 a <- delayedAssertion "getProcessInfo remotePid failed" n True
111 a <- delayedAssertion "getProcessInfo remotePid failed" n True
134 return a
112 return a
135
113
136−tests :: LocalNode -> IO [Test]
114+tests :: TestTransport -> IO [Test]
137−tests node1 = do
115+tests testtrans@TestTransport{..} = do
116+ node1 <- newLocalNode testTransport initRemoteTable
138 return [
117 return [
139 testGroup "Process Info" [
118 testGroup "Process Info" [
140 testCase "testLocalDeadProcessInfo"
119 testCase "testLocalDeadProcessInfo"
@@ -146,19 +125,5 @@ tests node1 = do
146 "expected process-info to be correctly populated"
125 "expected process-info to be correctly populated"
147 node1 True testLocalLiveProcessInfo)
126 node1 True testLocalLiveProcessInfo)
148 , testCase "testRemoveLiveProcessInfo"
127 , testCase "testRemoveLiveProcessInfo"
149− (testRemoteLiveProcessInfo node1)
128+ (testRemoteLiveProcessInfo testtrans node1)
150 ] ]
129 ] ]
151−
152−mkNode :: String -> IO LocalNode
153−mkNode port = do
154− Right (transport1, _) <- createTransportExposeInternals
155− "127.0.0.1" port defaultTCPParameters
156− newLocalNode transport1 initRemoteTable
157−
158−main :: IO ()
159−main = do
160− node1 <- mkNode "8081"
161− testData <- tests node1
162− defaultMain testData
163− closeLocalNode node1
164− return ()
tests/TestTracing.hs → distributed-process-tests/src/Control/Distributed/Process/Tests/Tracing.hsrenamed+155−104
@@ -1,7 +1,11 @@
1{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2{-# OPTIONS_GHC -fno-warn-orphans #-}
2{-# OPTIONS_GHC -fno-warn-orphans #-}
3−module Main where
3+module Control.Distributed.Process.Tests.Tracing (tests) where
4
4
5+import Control.Distributed.Process.Tests.Internal.Utils
6+import Network.Transport.Test (TestTransport(..))
7+
8+import Control.Applicative ((<*))
5import Control.Concurrent (threadDelay)
9import Control.Concurrent (threadDelay)
6import Control.Concurrent.MVar
10import Control.Concurrent.MVar
7 ( MVar
11 ( MVar
@@ -11,64 +15,29 @@ import Control.Concurrent.MVar
11 , takeMVar
15 , takeMVar
12 )
16 )
13import Control.Distributed.Process
17import Control.Distributed.Process
14−import Control.Distributed.Process.Debug
15import Control.Distributed.Process.Node
18import Control.Distributed.Process.Node
16− ( forkProcess
19+import Control.Distributed.Process.Debug
17− , newLocalNode
20+import Control.Distributed.Process.Management
18− , initRemoteTable
21+ ( MxEvent(..)
19− , closeLocalNode
22+ )
20− , LocalNode)
21−import Control.Exception as Ex (catch)
23+import qualified Control.Exception as IO (bracket)
22−import Network.Transport.TCP
24+import Data.List (isPrefixOf, isSuffixOf)
25+
23#if ! MIN_VERSION_base(4,6,0)
26#if ! MIN_VERSION_base(4,6,0)
24import Prelude hiding (catch, log)
27import Prelude hiding (catch, log)
28+#else
29+import Prelude hiding ((<*))
25#endif
30#endif
31+
26import Test.Framework
32import Test.Framework
27 ( Test
33 ( Test
28− , defaultMain
29 , testGroup
34 , testGroup
30 )
35 )
31−import Test.HUnit (Assertion)
32−import Test.HUnit.Base (assertBool)
33import Test.Framework.Providers.HUnit (testCase)
36import 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)
34
40
35−import System.Environment (getEnv)
36−import System.Posix.Env
37− ( setEnv
38− )
39−
40−-- these utilities have been cribbed from distributed-process-platform
41−-- we should really find a way to share them...
42−
43−-- | A mutable cell containing a test result.
44−type TestResult a = MVar a
45−
46−delayedAssertion :: (Eq a)
47− => String
48− -> LocalNode
49− -> a
50− -> (TestResult a -> Process ())
51− -> MVar ()
52− -> Assertion
53−delayedAssertion note localNode expected testProc lock = do
54− result <- newEmptyMVar
55− _ <- forkProcess localNode $ do
56− acquire lock
57− finally (testProc result)
58− (release lock)
59− assertComplete note result expected
60− where acquire lock' = liftIO $ takeMVar lock'
61− release lock' = liftIO $ putMVar lock' ()
62−
63−assertComplete :: (Eq a) => String -> MVar a -> a -> IO ()
64−assertComplete msg mv a = do
65− b <- takeMVar mv
66− assertBool msg (a == b)
67−
68−stash :: TestResult a -> a -> Process ()
69−stash mvar x = liftIO $ putMVar mvar x
70−
71−------
72
41
73testSpawnTracing :: TestResult Bool -> Process ()
42testSpawnTracing :: TestResult Bool -> Process ()
74testSpawnTracing result = do
43testSpawnTracing result = do
@@ -81,8 +50,8 @@ testSpawnTracing result = do
81 evDied <- liftIO $ newEmptyMVar
50 evDied <- liftIO $ newEmptyMVar
82 tracer <- startTracer $ \ev -> do
51 tracer <- startTracer $ \ev -> do
83 case ev of
52 case ev of
84− (TraceEvSpawned p) -> liftIO $ putMVar evSpawned p
53+ (MxSpawned p) -> liftIO $ putMVar evSpawned p
85− (TraceEvDied p r) -> liftIO $ putMVar evDied (p, r)
54+ (MxProcessDied p r) -> liftIO $ putMVar evDied (p, r)
86 _ -> return ()
55 _ -> return ()
87
56
88 (sp, rp) <- newChan
57 (sp, rp) <- newChan
@@ -115,7 +84,7 @@ testTraceRecvExplicitPid result = do
115 withTracer
84 withTracer
116 (\ev ->
85 (\ev ->
117 case ev of
86 case ev of
118− (TraceEvReceived pid' _) -> stash res (pid == pid')
87+ (MxReceived pid' _) -> stash res (pid == pid')
119 _ -> return ()) $ do
88 _ -> return ()) $ do
120 (sp, rp) <- newChan
89 (sp, rp) <- newChan
121 send pid sp
90 send pid sp
@@ -137,7 +106,7 @@ testTraceRecvNamedPid result = do
137 withTracer
106 withTracer
138 (\ev ->
107 (\ev ->
139 case ev of
108 case ev of
140− (TraceEvReceived pid' _) -> stash res (pid == pid')
109+ (MxReceived pid' _) -> stash res (pid == pid')
141 _ -> return ()) $ do
110 _ -> return ()) $ do
142 (sp, rp) <- newChan
111 (sp, rp) <- newChan
143 send pid sp
112 send pid sp
@@ -155,10 +124,12 @@ testTraceSending result = do
155 withTracer
124 withTracer
156 (\ev ->
125 (\ev ->
157 case ev of
126 case ev of
158− (TraceEvSent to from msg) -> do
127+ (MxSent to from msg) -> do
159− (Just s) <- unwrapMessage msg :: Process (Maybe String)
128+ mS <- unwrapMessage msg :: Process (Maybe String)
129+ case mS of
160− stash res (to == pid && from == self && s == "hello there")
130+ (Just s) -> do stash res (to == pid && from == self && s == "hello there")
161− stash res (to == pid && from == self)
131+ stash res (to == pid && from == self)
132+ _ -> die "failed state invariant, message type unmatched..."
162 _ ->
133 _ ->
163 return ()) $ do
134 return ()) $ do
164 send pid "hello there"
135 send pid "hello there"
@@ -180,7 +151,7 @@ testTraceRegistration result = do
180 withTracer
151 withTracer
181 (\ev ->
152 (\ev ->
182 case ev of
153 case ev of
183− TraceEvRegistered p s ->
154+ MxRegistered p s ->
184 stash res (p == pid && s == "foobar")
155 stash res (p == pid && s == "foobar")
185 _ ->
156 _ ->
186 return ()) $ do
157 return ()) $ do
@@ -207,7 +178,7 @@ testTraceUnRegistration result = do
207 withTracer
178 withTracer
208 (\ev ->
179 (\ev ->
209 case ev of
180 case ev of
210− TraceEvUnRegistered p n -> do
181+ MxUnRegistered p n -> do
211 stash res (p == pid && n == "foobar")
182 stash res (p == pid && n == "foobar")
212 send pid ()
183 send pid ()
213 _ ->
184 _ ->
@@ -241,21 +212,21 @@ testTraceLayering result = do
241 withTracer
212 withTracer
242 (\ev ->
213 (\ev ->
243 case ev of
214 case ev of
244− TraceEvDied _ _ -> liftIO $ putMVar died ()
215+ MxProcessDied _ _ -> liftIO $ putMVar died ()
245− _ -> return ())
216+ _ -> return ())
246 ( do {
217 ( do {
247 recv <- liftIO $ newEmptyMVar
218 recv <- liftIO $ newEmptyMVar
248 ; withTracer
219 ; withTracer
249 (\ev' ->
220 (\ev' ->
250 case ev' of
221 case ev' of
251− TraceEvReceived _ _ -> liftIO $ putMVar recv ()
222+ MxReceived _ _ -> liftIO $ putMVar recv ()
252 _ -> return ())
223 _ -> return ())
253 ( do {
224 ( do {
254 user <- liftIO $ newEmptyMVar
225 user <- liftIO $ newEmptyMVar
255 ; withTracer
226 ; withTracer
256 (\ev'' ->
227 (\ev'' ->
257 case ev'' of
228 case ev'' of
258− TraceEvUser _ -> liftIO $ putMVar user ()
229+ MxUser _ -> liftIO $ putMVar user ()
259 _ -> return ())
230 _ -> return ())
260 (send pid () >> (liftIO $ takeMVar user))
231 (send pid () >> (liftIO $ takeMVar user))
261 ; liftIO $ takeMVar recv
232 ; liftIO $ takeMVar recv
@@ -264,11 +235,11 @@ testTraceLayering result = do
264 })
235 })
265 liftIO $ putMVar result' ()
236 liftIO $ putMVar result' ()
266
237
267−testRemoteTraceRelay :: TestResult Bool -> Process ()
238+testRemoteTraceRelay :: TestTransport -> TestResult Bool -> Process ()
268−testRemoteTraceRelay result =
239+testRemoteTraceRelay TestTransport{..} result =
269 let flags = defaultTraceFlags { traceSpawned = traceOn }
240 let flags = defaultTraceFlags { traceSpawned = traceOn }
270 in do
241 in do
271− node2 <- liftIO $ mkNode "8082"
242+ node2 <- liftIO $ newLocalNode testTransport initRemoteTable
272 mvNid <- liftIO $ newEmptyMVar
243 mvNid <- liftIO $ newEmptyMVar
273
244
274 -- As well as needing node2's NodeId, we want to
245 -- As well as needing node2's NodeId, we want to
@@ -276,13 +247,9 @@ testRemoteTraceRelay result =
276 -- garbage on stderr for the duration of the test run.
247 -- garbage on stderr for the duration of the test run.
277 -- Here we set up that relay, and then wait for a signal
248 -- Here we set up that relay, and then wait for a signal
278 -- that the tracer (on node1) has seen the expected
249 -- that the tracer (on node1) has seen the expected
279− -- TraceEvSpawned message, at which point we're finished
250+ -- MxSpawned message, at which point we're finished
280− (Just log') <- whereis "logger"
281− pid <- liftIO $ forkProcess node2 $ do
282− logRelay <- spawnLocal $ relay log'
283− reregister "logger" logRelay
284− getSelfNode >>= stash mvNid >> (expect :: Process ())
285
251
252+ pid <- splinchLogger node2 mvNid
286 nid <- liftIO $ takeMVar mvNid
253 nid <- liftIO $ takeMVar mvNid
287 mref <- monitor pid
254 mref <- monitor pid
288 observedPid <- liftIO $ newEmptyMVar
255 observedPid <- liftIO $ newEmptyMVar
@@ -293,7 +260,7 @@ testRemoteTraceRelay result =
293 withTracer
260 withTracer
294 (\ev ->
261 (\ev ->
295 case ev of
262 case ev of
296− TraceEvSpawned p -> stash observedPid p >> send pid ()
263+ MxSpawned p -> stash observedPid p >> send pid ()
297 _ -> return ()) $ do
264 _ -> return ()) $ do
298 relayPid <- startTraceRelay nid
265 relayPid <- startTraceRelay nid
299 liftIO $ threadDelay 1000000
266 liftIO $ threadDelay 1000000
@@ -303,8 +270,8 @@ testRemoteTraceRelay result =
303 stash spawnedPid p
270 stash spawnedPid p
304
271
305 -- Now we wait for (the outer) pid to exit. This won't happen until
272 -- Now we wait for (the outer) pid to exit. This won't happen until
306− -- our tracer has seen the trace event for `p' and send `p' the
273+ -- our tracer has seen the trace event for `p' and sent `p' the
307− -- message its waiting for prior to exiting
274+ -- message it's waiting for prior to exiting
308 receiveWait [
275 receiveWait [
309 matchIf (\(ProcessMonitorNotification mref' _ _) -> mref == mref')
276 matchIf (\(ProcessMonitorNotification mref' _ _) -> mref == mref')
310 (\_ -> return ())
277 (\_ -> return ())
@@ -322,8 +289,99 @@ testRemoteTraceRelay result =
322 -- and just to be polite...
289 -- and just to be polite...
323 liftIO $ closeLocalNode node2
290 liftIO $ closeLocalNode node2
324
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+
325−tests :: LocalNode -> IO [Test]
382+tests :: TestTransport -> IO [Test]
326−tests node1 = do
383+tests testtrans@TestTransport{..} = do
384+ node1 <- newLocalNode testTransport initRemoteTable
327 -- if we execute the test cases in parallel, the
385 -- if we execute the test cases in parallel, the
328 -- various tracers will race with one another and
386 -- various tracers will race with one another and
329 -- we'll get garbage results (or worse, deadlocks)
387 -- we'll get garbage results (or worse, deadlocks)
@@ -331,50 +389,43 @@ tests node1 = do
331 return [
389 return [
332 testGroup "Tracing" [
390 testGroup "Tracing" [
333 testCase "Spawn Tracing"
391 testCase "Spawn Tracing"
334− (delayedAssertion
392+ (synchronisedAssertion
335 "expected dead process-info to be ProcessInfoNone"
393 "expected dead process-info to be ProcessInfoNone"
336 node1 True testSpawnTracing lock)
394 node1 True testSpawnTracing lock)
337 , testCase "Recv Tracing (Explicit Pid)"
395 , testCase "Recv Tracing (Explicit Pid)"
338− (delayedAssertion
396+ (synchronisedAssertion
339 "expected a recv trace for the supplied pid"
397 "expected a recv trace for the supplied pid"
340 node1 True testTraceRecvExplicitPid lock)
398 node1 True testTraceRecvExplicitPid lock)
341 , testCase "Recv Tracing (Named Pid)"
399 , testCase "Recv Tracing (Named Pid)"
342− (delayedAssertion
400+ (synchronisedAssertion
343 "expected a recv trace for the process registered as 'foobar'"
401 "expected a recv trace for the process registered as 'foobar'"
344 node1 True testTraceRecvNamedPid lock)
402 node1 True testTraceRecvNamedPid lock)
345 , testCase "Trace Send(er)"
403 , testCase "Trace Send(er)"
346− (delayedAssertion
404+ (synchronisedAssertion
347 "expected a 'send' trace with the requisite fields set"
405 "expected a 'send' trace with the requisite fields set"
348 node1 True testTraceSending lock)
406 node1 True testTraceSending lock)
349 , testCase "Trace Registration"
407 , testCase "Trace Registration"
350− (delayedAssertion
408+ (synchronisedAssertion
351 "expected a 'registered' trace"
409 "expected a 'registered' trace"
352 node1 True testTraceRegistration lock)
410 node1 True testTraceRegistration lock)
353 , testCase "Trace Unregistration"
411 , testCase "Trace Unregistration"
354− (delayedAssertion
412+ (synchronisedAssertion
355 "expected an 'unregistered' trace"
413 "expected an 'unregistered' trace"
356 node1 True testTraceUnRegistration lock)
414 node1 True testTraceUnRegistration lock)
357 , testCase "Trace Layering"
415 , testCase "Trace Layering"
358− (delayedAssertion
416+ (synchronisedAssertion
359− "expected blah"
417+ "expected blah"
360− node1 () testTraceLayering lock)
418+ node1 () testTraceLayering lock)
361 , testCase "Remote Trace Relay"
419 , testCase "Remote Trace Relay"
362− (delayedAssertion
420+ (synchronisedAssertion
363− "expected blah"
421+ "expected blah"
364− node1 True testRemoteTraceRelay lock)
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+ ]
365 ] ]
431 ] ]
366−
367−mkNode :: String -> IO LocalNode
368−mkNode port = do
369− Right (transport1, _) <- createTransportExposeInternals
370− "127.0.0.1" port defaultTCPParameters
371− newLocalNode transport1 initRemoteTable
372−
373−main :: IO ()
374−main = do
375− node1 <- mkNode "8081"
376− testData <- tests node1
377− defaultMain testData
378− closeLocalNode node1
379− return ()
380−
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+120−221
@@ -1,15 +1,15 @@
1Name: distributed-process
1Name: distributed-process
2−Version: 0.4.2
2+Version: 0.7.4
3Cabal-Version: >=1.8
3Cabal-Version: >=1.8
4Build-Type: Simple
4Build-Type: Simple
5License: BSD3
5License: BSD3
6License-File: LICENSE
6License-File: LICENSE
7−Copyright: Well-Typed LLP
7+Copyright: Well-Typed LLP, Tweag I/O Limited
8Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
8Author: 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>
10Stability: experimental
10Stability: experimental
11Homepage: http://haskell-distributed.github.com/
11Homepage: http://haskell-distributed.github.com/
12−Bug-Reports: http://cloud-haskell.atlassian.net
12+Bug-Reports: https://github.com/haskell-distributed/distributed-process/issues
13Synopsis: Cloud Haskell: Erlang-style concurrency in Haskell
13Synopsis: Cloud Haskell: Erlang-style concurrency in Haskell
14Description: This is an implementation of Cloud Haskell, as described in
14Description: This is an implementation of Cloud Haskell, as described in
15 /Towards Haskell in the Cloud/ by Jeff Epstein, Andrew Black,
15 /Towards Haskell in the Cloud/ by Jeff Epstein, Andrew Black,
@@ -17,12 +17,13 @@ Description: This is an implementation of Cloud Haskell, as described in
17 (<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),
17 (<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),
18 although some of the details are different. The precise message
18 although some of the details are different. The precise message
19 passing semantics are based on /A unified semantics for future Erlang/
19 passing semantics are based on /A unified semantics for future Erlang/
20− by Hans Svensson, Lars-Åke Fredlund and Clara Benac Earle.
20+ by Hans Svensson, Lars-&#xc5;ke Fredlund and Clara Benac Earle.
21
21
22 You will probably also want to install a Cloud Haskell backend such
22 You will probably also want to install a Cloud Haskell backend such
23 as distributed-process-simplelocalnet.
23 as distributed-process-simplelocalnet.
24−Tested-With: GHC==7.2.2 GHC==7.4.1 GHC==7.4.2
24+Tested-With: GHC==7.10.3 GHC==8.0.2 GHC==8.2.2 GHC==8.4.4
25Category: Control
25Category: Control
26+extra-source-files: ChangeLog
26
27
27Source-Repository head
28Source-Repository head
28 Type: git
29 Type: git
@@ -33,237 +34,135 @@ flag th
33 description: Build with Template Haskell support
34 description: Build with Template Haskell support
34 default: True
35 default: True
35
36
36−flag benchmarks
37+flag old-locale
37− description: Build benchmarks
38+ description: If false then depend on time >= 1.5.
38− default: False
39+ .
39−
40+ If true then depend on time < 1.5 together with old-locale.
40−flag prof
41− description: Compiling with profiling enabled
42− default: False
41+ default: False
43
42
44Library
43Library
45− Build-Depends: base >= 4.4 && < 5,
44+ Build-Depends: base >= 4.6 && < 5,
46− binary >= 0.5 && < 0.8,
45+ binary >= 0.6.3 && < 0.10,
47− deepseq >= 1.3.0.1 && < 1.4,
48 hashable >= 1.2.0.5 && < 1.3,
46 hashable >= 1.2.0.5 && < 1.3,
49− network-transport >= 0.3 && < 0.4,
47+ network-transport >= 0.4.1.0 && < 0.6,
50− stm >= 2.4 && < 2.5,
48+ stm >= 2.4 && < 2.6,
51− transformers >= 0.2 && < 0.4,
49+ transformers >= 0.2 && < 0.6,
52− mtl >= 2.0 && < 2.2,
50+ mtl >= 2.0 && < 2.4,
53 data-accessor >= 0.2 && < 0.3,
51 data-accessor >= 0.2 && < 0.3,
54 bytestring >= 0.9 && < 0.11,
52 bytestring >= 0.9 && < 0.11,
55− containers >= 0.4 && < 0.6,
56− old-locale >= 1.0 && < 1.1,
57− time >= 1.2 && < 1.5,
58− random >= 1.0 && < 1.1,
53+ random >= 1.0 && < 1.2,
59− ghc-prim >= 0.2 && < 0.4,
60− distributed-static >= 0.2 && < 0.3,
54+ distributed-static >= 0.2 && < 0.4,
61− rank1dynamic >= 0.1 && < 0.2,
55+ rank1dynamic >= 0.1 && < 0.5,
62− syb >= 0.3 && < 0.4
56+ syb >= 0.3 && < 0.8,
57+ exceptions >= 0.5,
58+ containers >= 0.5 && < 0.7,
59+ deepseq >= 1.3.0.1 && < 1.6
63 Exposed-modules: Control.Distributed.Process,
60 Exposed-modules: Control.Distributed.Process,
64 Control.Distributed.Process.Closure,
61 Control.Distributed.Process.Closure,
65 Control.Distributed.Process.Debug,
62 Control.Distributed.Process.Debug,
66− Control.Distributed.Process.Internal.Types,
63+ Control.Distributed.Process.Internal.BiMultiMap,
67− Control.Distributed.Process.Management,
68− Control.Distributed.Process.Management.Agent,
69− Control.Distributed.Process.Node,
70− Control.Distributed.Process.Serializable,
71− Control.Distributed.Process.UnsafePrimitives
72− Other-modules: Control.Distributed.Process.Internal.Primitives,
73− Control.Distributed.Process.Management.Bus,
74− Control.Distributed.Process.Internal.Spawn,
75− Control.Distributed.Process.Internal.Primitives,
76− Control.Distributed.Process.Internal.CQueue,
77 Control.Distributed.Process.Internal.Closure.BuiltIn,
64 Control.Distributed.Process.Internal.Closure.BuiltIn,
65+ Control.Distributed.Process.Internal.Closure.Explicit,
66+ Control.Distributed.Process.Internal.CQueue,
78 Control.Distributed.Process.Internal.Messaging,
67 Control.Distributed.Process.Internal.Messaging,
68+ Control.Distributed.Process.Internal.Primitives,
69+ Control.Distributed.Process.Internal.Spawn,
70+ Control.Distributed.Process.Internal.StrictContainerAccessors,
79 Control.Distributed.Process.Internal.StrictList,
71 Control.Distributed.Process.Internal.StrictList,
80 Control.Distributed.Process.Internal.StrictMVar,
72 Control.Distributed.Process.Internal.StrictMVar,
73+ Control.Distributed.Process.Internal.Types,
81 Control.Distributed.Process.Internal.WeakTQueue,
74 Control.Distributed.Process.Internal.WeakTQueue,
82− Control.Distributed.Process.Internal.StrictContainerAccessors,
75+ Control.Distributed.Process.Management,
76+ Control.Distributed.Process.Node,
77+ Control.Distributed.Process.Serializable,
78+ Control.Distributed.Process.UnsafePrimitives
79+ Control.Distributed.Process.Management.Internal.Agent,
83− Control.Distributed.Process.Internal.Trace.Types,
80+ Control.Distributed.Process.Management.Internal.Bus,
81+ Control.Distributed.Process.Management.Internal.Types,
84− Control.Distributed.Process.Internal.Trace.Primitives,
82+ Control.Distributed.Process.Management.Internal.Trace.Primitives,
85− Control.Distributed.Process.Internal.Trace.Tracer,
83+ Control.Distributed.Process.Management.Internal.Trace.Remote,
86− Control.Distributed.Process.Internal.Trace.Remote
84+ Control.Distributed.Process.Management.Internal.Trace.Types,
87− Extensions: RankNTypes,
85+ Control.Distributed.Process.Management.Internal.Trace.Tracer
88− ScopedTypeVariables,
89− FlexibleInstances,
90− UndecidableInstances,
91− ExistentialQuantification,
92− GADTs,
93− GeneralizedNewtypeDeriving,
94− DeriveDataTypeable,
95− CPP,
96− BangPatterns
97 ghc-options: -Wall
86 ghc-options: -Wall
98 HS-Source-Dirs: src
87 HS-Source-Dirs: src
99− if flag(th)
88+ other-extensions: BangPatterns
100− Build-Depends: template-haskell >= 2.6 && < 2.9
89+ CPP
101− Exposed-modules: Control.Distributed.Process.Internal.Closure.TH
102− CPP-Options: -DTemplateHaskellSupport
103−
104−Test-Suite TestCH
105− Type: exitcode-stdio-1.0
106− Main-Is: TestCH.hs
107− Build-Depends: base >= 4.4 && < 5,
108− random >= 1.0 && < 1.1,
109− ansi-terminal >= 0.5 && < 0.6,
110− distributed-process,
111− network-transport >= 0.3 && < 0.4,
112− network-transport-tcp >= 0.3 && < 0.4,
113− binary >= 0.5 && < 0.8,
114− network >= 2.3 && < 2.5,
115− HUnit >= 1.2 && < 1.3,
116− test-framework >= 0.6 && < 0.9,
117− test-framework-hunit >= 0.2.0 && < 0.4,
118− rematch >= 0.1.2.1
119− Extensions: CPP,
120− ScopedTypeVariables,
121− DeriveDataTypeable,
90+ DeriveDataTypeable
91+ DeriveFunctor
92+ DeriveGeneric
93+ ExistentialQuantification
94+ FlexibleInstances
95+ GADTs
122 GeneralizedNewtypeDeriving
96 GeneralizedNewtypeDeriving
123− ghc-options: -Wall -threaded -debug -eventlog -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
97+ KindSignatures
124− HS-Source-Dirs: tests
98+ MagicHash
125−
99+ PatternGuards
126−Test-Suite TestClosure
100+ RankNTypes
127− Type: exitcode-stdio-1.0
101+ RecordWildCards
128− Main-Is: TestClosure.hs
129− Build-Depends: base >= 4.4 && < 5,
130− random >= 1.0 && < 1.1,
131− ansi-terminal >= 0.5 && < 0.6,
132− distributed-static >= 0.2 && < 0.3,
133− distributed-process,
134− network-transport >= 0.3 && < 0.4,
135− network-transport-tcp >= 0.3 && < 0.4,
136− bytestring >= 0.9 && < 0.11,
137− network >= 2.3 && < 2.5,
138− HUnit >= 1.2 && < 1.3,
139− test-framework >= 0.6 && < 0.9,
140− test-framework-hunit >= 0.2.0 && < 0.4
141− Extensions: CPP,
142 ScopedTypeVariables
102 ScopedTypeVariables
143− ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
103+ StandaloneDeriving
144− HS-Source-Dirs: tests
104+ TypeFamilies
145−
105+ TypeSynonymInstances
146−Test-Suite TestStats
106+ UnboxedTuples
147− Type: exitcode-stdio-1.0
148− Main-Is: TestStats.hs
149− Build-Depends: base >= 4.4 && < 5,
150− random >= 1.0 && < 1.1,
151− ansi-terminal >= 0.5 && < 0.6,
152− containers >= 0.4 && < 0.6,
153− stm >= 2.4 && < 2.5,
154− distributed-process,
155− network-transport >= 0.3 && < 0.4,
156− network-transport-tcp >= 0.3 && < 0.4,
157− binary >= 0.5 && < 0.8,
158− network >= 2.3 && < 2.5,
159− HUnit >= 1.2 && < 1.3,
160− test-framework >= 0.6 && < 0.9,
161− test-framework-hunit >= 0.2.0 && < 0.4
162− Extensions: CPP,
163− ScopedTypeVariables,
164− DeriveDataTypeable,
165− GeneralizedNewtypeDeriving
166− ghc-options: -Wall -debug -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
167− HS-Source-Dirs: tests
168−
169−Test-Suite TestTracing
170− Type: exitcode-stdio-1.0
171− Main-Is: TestTracing.hs
172− Build-Depends: base >= 4.4 && < 5,
173− random >= 1.0 && < 1.1,
174− ansi-terminal >= 0.5 && < 0.6,
175− containers >= 0.4 && < 0.6,
176− stm >= 2.4 && < 2.5,
177− distributed-process,
178− network-transport >= 0.3 && < 0.4,
179− network-transport-tcp >= 0.3 && < 0.4,
180− binary >= 0.5 && < 0.7,
181− network >= 2.3 && < 2.5,
182− HUnit >= 1.2 && < 1.3,
183− test-framework >= 0.6 && < 0.9,
184− test-framework-hunit >= 0.2.0 && < 0.4,
185− unix >= 2.5.0.0
186− Extensions: RankNTypes,
187− ScopedTypeVariables,
188− FlexibleInstances,
189− UndecidableInstances,
107+ UndecidableInstances
190− ExistentialQuantification,
108+ if flag(old-locale)
191− GADTs,
192− GeneralizedNewtypeDeriving,
193− DeriveDataTypeable,
194− CPP,
195− BangPatterns
196− ghc-options: -Wall -debug -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
197− HS-Source-Dirs: tests
198−
199−
200−Executable distributed-process-throughput
201− if flag(benchmarks)
202− Build-Depends: base >= 4.4 && < 5,
203− distributed-process,
204− network-transport-tcp >= 0.3 && < 0.4,
205− bytestring >= 0.9 && < 0.11,
206− binary >= 0.5 && < 0.8
207− else
208− buildable: False
209− Main-Is: benchmarks/Throughput.hs
210− ghc-options: -Wall
211− Extensions: BangPatterns
212−
213−Executable distributed-process-latency
214− if flag(benchmarks)
215− Build-Depends: base >= 4.4 && < 5,
109+ Build-Depends: time < 1.5, old-locale >= 1.0 && <1.1
216− distributed-process,
217− network-transport-tcp >= 0.3 && < 0.4,
218− bytestring >= 0.9 && < 0.11,
219− binary >= 0.5 && < 0.8
220− else
221− buildable: False
222− Main-Is: benchmarks/Latency.hs
223− ghc-options: -Wall
224− Extensions: BangPatterns
225−
226−Executable distributed-process-channels
227− if flag(benchmarks)
228− Build-Depends: base >= 4.4 && < 5,
229− distributed-process,
230− network-transport-tcp >= 0.3 && < 0.4,
231− bytestring >= 0.9 && < 0.11,
232− binary >= 0.5 && < 0.8
233− else
234− buildable: False
235− Main-Is: benchmarks/Channels.hs
236− ghc-options: -Wall
237− Extensions: BangPatterns
238−
239−Executable distributed-process-spawns
240− if flag(benchmarks)
241− Build-Depends: base >= 4.4 && < 5,
242− distributed-process,
243− network-transport-tcp >= 0.3 && < 0.4,
244− bytestring >= 0.9 && < 0.11,
245− binary >= 0.5 && < 0.8
246− else
247− buildable: False
248− Main-Is: benchmarks/Spawns.hs
249− ghc-options: -Wall
250− Extensions: BangPatterns
251−
252−Executable distributed-process-ring
253− if flag(benchmarks)
254− Build-Depends: base >= 4.4 && < 5,
255− distributed-process,
256− network-transport-tcp >= 0.3 && < 0.4,
257− bytestring >= 0.9 && < 0.11,
258− binary >= 0.5 && < 0.8
259 else
110 else
260− buildable: False
111+ Build-Depends: time >= 1.5
261− Main-Is: benchmarks/ProcessRing.hs
262− if flag(prof)
112+ if flag(th)
263− ghc-options: -Wall -threaded -prof -auto-all -fno-prof-count-entries -rtsopts
113+ other-extensions: TemplateHaskell
264− else
114+ Build-Depends: template-haskell >= 2.6
265− ghc-options: -Wall -threaded -O2 -rtsopts
115+ Exposed-modules: Control.Distributed.Process.Internal.Closure.TH
266− Extensions: BangPatterns,
116+ CPP-Options: -DTemplateHaskellSupport
267− ScopedTypeVariables
268−
269
117
118+-- Tests are in distributed-process-test package, for convenience.
119+
120+benchmark distributed-process-throughput
121+ Type: exitcode-stdio-1.0
122+ Build-Depends: base >= 4.6 && < 5,
123+ distributed-process,
124+ network-transport-tcp >= 0.3 && < 0.7,
125+ bytestring >= 0.9 && < 0.11,
126+ binary >= 0.6.3 && < 0.10
127+ Main-Is: benchmarks/Throughput.hs
128+ ghc-options: -Wall
129+
130+benchmark distributed-process-latency
131+ Type: exitcode-stdio-1.0
132+ Build-Depends: base >= 4.6 && < 5,
133+ distributed-process,
134+ network-transport-tcp >= 0.3 && < 0.7,
135+ bytestring >= 0.9 && < 0.11,
136+ binary >= 0.6.3 && < 0.10
137+ Main-Is: benchmarks/Latency.hs
138+ ghc-options: -Wall
139+
140+benchmark distributed-process-channels
141+ Type: exitcode-stdio-1.0
142+ Build-Depends: base >= 4.6 && < 5,
143+ distributed-process,
144+ network-transport-tcp >= 0.3 && < 0.7,
145+ bytestring >= 0.9 && < 0.11,
146+ binary >= 0.6.3 && < 0.10
147+ Main-Is: benchmarks/Channels.hs
148+ ghc-options: -Wall
149+
150+benchmark distributed-process-spawns
151+ Type: exitcode-stdio-1.0
152+ Build-Depends: base >= 4.6 && < 5,
153+ distributed-process,
154+ network-transport-tcp >= 0.3 && < 0.7,
155+ bytestring >= 0.9 && < 0.11,
156+ binary >= 0.6.3 && < 0.10
157+ Main-Is: benchmarks/Spawns.hs
158+ ghc-options: -Wall
159+
160+benchmark distributed-process-ring
161+ Type: exitcode-stdio-1.0
162+ Build-Depends: base >= 4.6 && < 5,
163+ distributed-process,
164+ network-transport-tcp >= 0.3 && < 0.7,
165+ bytestring >= 0.9 && < 0.11,
166+ binary >= 0.6.3 && < 0.10
167+ Main-Is: benchmarks/ProcessRing.hs
168+ ghc-options: -Wall -threaded -O2 -rtsopts
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+76−7
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
1{- | [Cloud Haskell]
2{- | [Cloud Haskell]
2
3
3This is an implementation of Cloud Haskell, as described in
4This is an implementation of Cloud Haskell, as described in
@@ -16,7 +17,7 @@ please see the distributed-process wiki page on github:
16module Control.Distributed.Process
17module Control.Distributed.Process
17 ( -- * Basic types
18 ( -- * Basic types
18 ProcessId
19 ProcessId
19− , NodeId
20+ , NodeId(..)
20 , Process
21 , Process
21 , SendPortId
22 , SendPortId
22 , processNodeId
23 , processNodeId
@@ -24,6 +25,7 @@ module Control.Distributed.Process
24 , liftIO -- Reexported for convenience
25 , liftIO -- Reexported for convenience
25 -- * Basic messaging
26 -- * Basic messaging
26 , send
27 , send
28+ , usend
27 , expect
29 , expect
28 , expectTimeout
30 , expectTimeout
29 -- * Channels
31 -- * Channels
@@ -38,8 +40,11 @@ module Control.Distributed.Process
38 , mergePortsRR
40 , mergePortsRR
39 -- * Unsafe messaging variants
41 -- * Unsafe messaging variants
40 , unsafeSend
42 , unsafeSend
43+ , unsafeUSend
41 , unsafeSendChan
44 , unsafeSendChan
42 , unsafeNSend
45 , unsafeNSend
46+ , unsafeNSendRemote
47+ , unsafeWrapMessage
43 -- * Advanced messaging
48 -- * Advanced messaging
44 , Match
49 , Match
45 , receiveWait
50 , receiveWait
@@ -50,6 +55,7 @@ module Control.Distributed.Process
50 , matchAny
55 , matchAny
51 , matchAnyIf
56 , matchAnyIf
52 , matchChan
57 , matchChan
58+ , matchSTM
53 , Message
59 , Message
54 , matchMessage
60 , matchMessage
55 , matchMessageIf
61 , matchMessageIf
@@ -58,7 +64,10 @@ module Control.Distributed.Process
58 , unwrapMessage
64 , unwrapMessage
59 , handleMessage
65 , handleMessage
60 , handleMessageIf
66 , handleMessageIf
67+ , handleMessage_
68+ , handleMessageIf_
61 , forward
69 , forward
70+ , uforward
62 , delegate
71 , delegate
63 , relay
72 , relay
64 , proxy
73 , proxy
@@ -78,6 +87,9 @@ module Control.Distributed.Process
78 , getSelfNode
87 , getSelfNode
79 , ProcessInfo(..)
88 , ProcessInfo(..)
80 , getProcessInfo
89 , getProcessInfo
90+ , NodeStats(..)
91+ , getNodeStats
92+ , getLocalNodeStats
81 -- * Monitoring and linking
93 -- * Monitoring and linking
82 , link
94 , link
83 , linkNode
95 , linkNode
@@ -90,6 +102,7 @@ module Control.Distributed.Process
90 , monitorPort
102 , monitorPort
91 , unmonitor
103 , unmonitor
92 , withMonitor
104 , withMonitor
105+ , withMonitor_
93 , MonitorRef -- opaque
106 , MonitorRef -- opaque
94 , ProcessLinkException(..)
107 , ProcessLinkException(..)
95 , NodeLinkException(..)
108 , NodeLinkException(..)
@@ -126,6 +139,7 @@ module Control.Distributed.Process
126 , catches
139 , catches
127 , try
140 , try
128 , mask
141 , mask
142+ , mask_
129 , onException
143 , onException
130 , bracket
144 , bracket
131 , bracket_
145 , bracket_
@@ -140,19 +154,22 @@ module Control.Distributed.Process
140 -- * Local versions of 'spawn'
154 -- * Local versions of 'spawn'
141 , spawnLocal
155 , spawnLocal
142 , spawnChannelLocal
156 , spawnChannelLocal
157+ , callLocal
143 -- * Reconnecting
158 -- * Reconnecting
144 , reconnect
159 , reconnect
145 , reconnectPort
160 , reconnectPort
146 ) where
161 ) where
147
162
148−#if ! MIN_VERSION_base(4,6,0)
149−import Prelude hiding (catch)
150−#endif
151−
152import Control.Monad.IO.Class (liftIO)
163import Control.Monad.IO.Class (liftIO)
153−import Control.Applicative ((<$>))
164+import Control.Applicative
154import Control.Monad.Reader (ask)
165import Control.Monad.Reader (ask)
155−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+ )
156import Control.Distributed.Static
173import Control.Distributed.Static
157 ( Closure
174 ( Closure
158 , closure
175 , closure
@@ -181,11 +198,13 @@ import Control.Distributed.Process.Internal.Types
181 , RegisterReply(..)
198 , RegisterReply(..)
182 , LocalProcess(processNode)
199 , LocalProcess(processNode)
183 , Message
200 , Message
201+ , localProcessWithId
184 )
202 )
185import Control.Distributed.Process.Serializable (Serializable)
203import Control.Distributed.Process.Serializable (Serializable)
186import Control.Distributed.Process.Internal.Primitives
204import Control.Distributed.Process.Internal.Primitives
187 ( -- Basic messaging
205 ( -- Basic messaging
188 send
206 send
207+ , usend
189 , expect
208 , expect
190 -- Channels
209 -- Channels
191 , newChan
210 , newChan
@@ -194,8 +213,10 @@ import Control.Distributed.Process.Internal.Primitives
194 , mergePortsBiased
213 , mergePortsBiased
195 , mergePortsRR
214 , mergePortsRR
196 , unsafeSend
215 , unsafeSend
216+ , unsafeUSend
197 , unsafeSendChan
217 , unsafeSendChan
198 , unsafeNSend
218 , unsafeNSend
219+ , unsafeNSendRemote
199 -- Advanced messaging
220 -- Advanced messaging
200 , Match
221 , Match
201 , receiveWait
222 , receiveWait
@@ -206,14 +227,19 @@ import Control.Distributed.Process.Internal.Primitives
206 , matchAny
227 , matchAny
207 , matchAnyIf
228 , matchAnyIf
208 , matchChan
229 , matchChan
230+ , matchSTM
209 , matchMessage
231 , matchMessage
210 , matchMessageIf
232 , matchMessageIf
211 , isEncoded
233 , isEncoded
212 , wrapMessage
234 , wrapMessage
235+ , unsafeWrapMessage
213 , unwrapMessage
236 , unwrapMessage
214 , handleMessage
237 , handleMessage
215 , handleMessageIf
238 , handleMessageIf
239+ , handleMessage_
240+ , handleMessageIf_
216 , forward
241 , forward
242+ , uforward
217 , delegate
243 , delegate
218 , relay
244 , relay
219 , proxy
245 , proxy
@@ -229,6 +255,9 @@ import Control.Distributed.Process.Internal.Primitives
229 , getSelfNode
255 , getSelfNode
230 , ProcessInfo(..)
256 , ProcessInfo(..)
231 , getProcessInfo
257 , getProcessInfo
258+ , NodeStats(..)
259+ , getNodeStats
260+ , getLocalNodeStats
232 -- Monitoring and linking
261 -- Monitoring and linking
233 , link
262 , link
234 , linkNode
263 , linkNode
@@ -241,6 +270,7 @@ import Control.Distributed.Process.Internal.Primitives
241 , monitorPort
270 , monitorPort
242 , unmonitor
271 , unmonitor
243 , withMonitor
272 , withMonitor
273+ , withMonitor_
244 -- Logging
274 -- Logging
245 , say
275 , say
246 -- Registry
276 -- Registry
@@ -263,6 +293,7 @@ import Control.Distributed.Process.Internal.Primitives
263 , catches
293 , catches
264 , try
294 , try
265 , mask
295 , mask
296+ , mask_
266 , onException
297 , onException
267 , bracket
298 , bracket
268 , bracket_
299 , bracket_
@@ -276,6 +307,10 @@ import Control.Distributed.Process.Internal.Primitives
276 , reconnectPort
307 , reconnectPort
277 )
308 )
278import Control.Distributed.Process.Node (forkProcess)
309import Control.Distributed.Process.Node (forkProcess)
310+import Control.Distributed.Process.Internal.Types
311+ ( processThread
312+ , withValidLocalState
313+ )
279import Control.Distributed.Process.Internal.Spawn
314import Control.Distributed.Process.Internal.Spawn
280 ( -- Spawning Processes/Channels
315 ( -- Spawning Processes/Channels
281 spawn
316 spawn
@@ -285,6 +320,17 @@ import Control.Distributed.Process.Internal.Spawn
285 , spawnSupervised
320 , spawnSupervised
286 , call
321 , call
287 )
322 )
323+import qualified Control.Monad.Catch as Catch
324+
325+#if MIN_VERSION_base(4,6,0)
326+import Prelude
327+#else
328+import Prelude hiding (catch)
329+#endif
330+import qualified Control.Exception as Exception (onException)
331+import Data.Accessor ((^.))
332+import Data.Foldable (forM_)
333+
288
334
289-- INTERNAL NOTES
335-- INTERNAL NOTES
290--
336--
@@ -376,3 +422,26 @@ spawnChannelLocal proc = do
376 proc rport
422 proc rport
377 takeMVar mvar
423 takeMVar mvar
378
424
425+-- | Local version of 'call'. Running a process in this way isolates it from
426+-- messages sent to the caller process, and also allows silently dropping late
427+-- or duplicate messages sent to the isolated process after it exits.
428+-- Silently dropping messages may not always be the best approach.
429+callLocal :: Process a -> Process a
430+callLocal proc = Catch.mask $ \release -> do
431+ mv <- liftIO newEmptyMVar :: Process (MVar (Either Catch.SomeException a))
432+ child <- spawnLocal $ Catch.try (release proc) >>= liftIO . putMVar mv
433+ lproc <- ask
434+ liftIO $ do
435+ rs <- Exception.onException (takeMVar mv) $ Catch.uninterruptibleMask_ $
436+ -- Exceptions need to be prevented from interrupting the clean up or
437+ -- the original exception which caused entering the handler could be
438+ -- forgotten. For instance, this could have a problematic effect
439+ -- when the original exception was meant to kill the thread and the
440+ -- second exception doesn't (like the exception thrown by
441+ -- 'System.Timeout.timeout').
442+ do mchildThreadId <- withValidLocalState (processNode lproc) $
443+ \vst -> return $ fmap processThread $
444+ vst ^. localProcessWithId (processLocalId child)
445+ forM_ mchildThreadId killThread
446+ takeMVar mv
447+ either Catch.throwM return rs
src/Control/Distributed/Process/Closure.hs+20
@@ -163,6 +163,8 @@ module Control.Distributed.Process.Closure
163 , sdictUnit
163 , sdictUnit
164 , sdictProcessId
164 , sdictProcessId
165 , sdictSendPort
165 , sdictSendPort
166+ , sdictStatic
167+ , sdictClosure
166 -- * The CP type and associated combinators
168 -- * The CP type and associated combinators
167 , CP
169 , CP
168 , idCP
170 , idCP
@@ -177,6 +179,13 @@ module Control.Distributed.Process.Closure
177 , cpSend
179 , cpSend
178 , cpExpect
180 , cpExpect
179 , cpNewChan
181 , cpNewChan
182+ -- * Working with static values and closures (without Template Haskell)
183+ , RemoteRegister
184+ , MkTDict(..)
185+ , mkStaticVal
186+ , mkClosureValSingle
187+ , mkClosureVal
188+ , call'
180#ifdef TemplateHaskellSupport
189#ifdef TemplateHaskellSupport
181 -- * Template Haskell support for creating static values and closures
190 -- * Template Haskell support for creating static values and closures
182 , remotable
191 , remotable
@@ -196,6 +205,8 @@ import Control.Distributed.Process.Internal.Closure.BuiltIn
196 , sdictUnit
205 , sdictUnit
197 , sdictProcessId
206 , sdictProcessId
198 , sdictSendPort
207 , sdictSendPort
208+ , sdictStatic
209+ , sdictClosure
199 -- The CP type and associated combinators
210 -- The CP type and associated combinators
200 , CP
211 , CP
201 , idCP
212 , idCP
@@ -211,6 +222,15 @@ import Control.Distributed.Process.Internal.Closure.BuiltIn
211 , cpExpect
222 , cpExpect
212 , cpNewChan
223 , cpNewChan
213 )
224 )
225+import Control.Distributed.Process.Internal.Closure.Explicit
226+ (
227+ RemoteRegister
228+ , MkTDict(..)
229+ , mkStaticVal
230+ , mkClosureValSingle
231+ , mkClosureVal
232+ , call'
233+ )
214#ifdef TemplateHaskellSupport
234#ifdef TemplateHaskellSupport
215import Control.Distributed.Process.Internal.Closure.TH
235import Control.Distributed.Process.Internal.Closure.TH
216 ( remotable
236 ( remotable
src/Control/Distributed/Process/Debug.hs+37−25
@@ -1,3 +1,6 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+{-# LANGUAGE RankNTypes #-}
1-----------------------------------------------------------------------------
4-----------------------------------------------------------------------------
2-- |
5-- |
3-- Module : Control.Distributed.Process.Debug
6-- Module : Control.Distributed.Process.Debug
@@ -20,10 +23,10 @@
20-- Throughout the lifecycle of a local node, the distributed-process runtime
23-- Throughout the lifecycle of a local node, the distributed-process runtime
21-- generates /trace events/, describing internal runtime activities such as
24-- generates /trace events/, describing internal runtime activities such as
22-- the spawning and death of processes, message sending, delivery and so on.
25-- the spawning and death of processes, message sending, delivery and so on.
23−-- See the 'TraceEvent' type's documentation for a list of all the published
26+-- See the 'MxEvent' type's documentation for a list of all the published
24−-- event types.
27+-- event types, which correspond directly to the types of /management/ events.
25-- Users can additionally publish custom trace events in the form of
28-- Users can additionally publish custom trace events in the form of
26−-- 'TraceEvLog' log messages or pass custom (i.e., completely user defined)
29+-- 'MxLog' log messages or pass custom (i.e., completely user defined)
27-- event data using the 'traceMessage' function.
30-- event data using the 'traceMessage' function.
28--
31--
29-- All published traces are forwarded to a /tracer process/, which can be
32-- All published traces are forwarded to a /tracer process/, which can be
@@ -75,6 +78,19 @@
75-- This is checked for /any/ non-empty value. If set, all internal traces are
78-- This is checked for /any/ non-empty value. If set, all internal traces are
76-- written to the GHC eventlog.
79-- written to the GHC eventlog.
77--
80--
81+-- By default, the built in tracers will ignore all trace events! In order to
82+-- enable tracing the incoming 'MxEvent' stream, the @DISTRIBUTED_PROCESS_TRACE_FLAGS@
83+-- environment variable accepts the following flags, which enable tracing specific
84+-- event types:
85+--
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
93+--
78-- Users of the /simplelocalnet/ Cloud Haskell backend should also note that
94-- Users of the /simplelocalnet/ Cloud Haskell backend should also note that
79-- because the trace file option only supports trace output from a single node
95-- because the trace file option only supports trace output from a single node
80-- (so as to avoid interleaving), a file trace configured for the master node
96-- (so as to avoid interleaving), a file trace configured for the master node
@@ -92,7 +108,6 @@
92module Control.Distributed.Process.Debug
108module Control.Distributed.Process.Debug
93 ( -- * Exported Data Types
109 ( -- * Exported Data Types
94 TraceArg(..)
110 TraceArg(..)
95− , TraceEvent(..)
96 , TraceFlags(..)
111 , TraceFlags(..)
97 , TraceSubject(..)
112 , TraceSubject(..)
98 -- * Configuring Tracing
113 -- * Configuring Tracing
@@ -126,17 +141,14 @@ module Control.Distributed.Process.Debug
126 )
141 )
127 where
142 where
128
143
129−import Control.Applicative ((<$>))
144+import Control.Applicative
130import Control.Distributed.Process.Internal.Primitives
145import Control.Distributed.Process.Internal.Primitives
131 ( proxy
146 ( proxy
132− , finally
133 , die
147 , die
134 , whereis
148 , whereis
135 , send
149 , send
136 , receiveWait
150 , receiveWait
137 , matchIf
151 , matchIf
138− , finally
139− , try
140 , monitor
152 , monitor
141 )
153 )
142import Control.Distributed.Process.Internal.Types
154import Control.Distributed.Process.Internal.Types
@@ -145,19 +157,21 @@ import Control.Distributed.Process.Internal.Types
145 , LocalProcess(..)
157 , LocalProcess(..)
146 , ProcessMonitorNotification(..)
158 , ProcessMonitorNotification(..)
147 )
159 )
148−import Control.Distributed.Process.Internal.Trace.Types
160+import Control.Distributed.Process.Management.Internal.Types
161+ ( MxEvent(..)
162+ )
163+import Control.Distributed.Process.Management.Internal.Trace.Types
149 ( TraceArg(..)
164 ( TraceArg(..)
150− , TraceEvent(..)
151 , TraceFlags(..)
165 , TraceFlags(..)
152 , TraceSubject(..)
166 , TraceSubject(..)
153 , defaultTraceFlags
167 , defaultTraceFlags
154 )
168 )
155−import Control.Distributed.Process.Internal.Trace.Tracer
169+import Control.Distributed.Process.Management.Internal.Trace.Tracer
156 ( systemLoggerTracer
170 ( systemLoggerTracer
157 , logfileTracer
171 , logfileTracer
158 , eventLogTracer
172 , eventLogTracer
159 )
173 )
160−import Control.Distributed.Process.Internal.Trace.Primitives
174+import Control.Distributed.Process.Management.Internal.Trace.Primitives
161 ( withRegisteredTracer
175 ( withRegisteredTracer
162 , enableTrace
176 , enableTrace
163 , enableTraceAsync
177 , enableTraceAsync
@@ -172,19 +186,18 @@ import Control.Distributed.Process.Internal.Trace.Primitives
172 , traceLogFmt
186 , traceLogFmt
173 , traceMessage
187 , traceMessage
174 )
188 )
175−import qualified Control.Distributed.Process.Internal.Trace.Remote as Remote
189+import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Remote
176import Control.Distributed.Process.Node
190import Control.Distributed.Process.Node
177
191
178import Control.Exception (SomeException)
192import Control.Exception (SomeException)
179
193
180import Control.Monad.IO.Class (liftIO)
194import Control.Monad.IO.Class (liftIO)
181import Control.Monad.Reader (ask)
195import Control.Monad.Reader (ask)
196+import Control.Monad.Catch (finally, try)
182
197
183import Data.Binary()
198import Data.Binary()
184
199
185−#if ! MIN_VERSION_base(4,6,0)
186−import Prelude hiding (catch)
200+import Prelude
187−#endif
188
201
189--------------------------------------------------------------------------------
202--------------------------------------------------------------------------------
190-- Debugging/Tracing API --
203-- Debugging/Tracing API --
@@ -197,7 +210,7 @@ import Prelude hiding (catch)
197-- through all the layers in turn. Once the top layer is stopped, the user
210-- through all the layers in turn. Once the top layer is stopped, the user
198-- is responsible for re-registering the original (prior) tracer pid before
211-- is responsible for re-registering the original (prior) tracer pid before
199-- terminating. See 'withTracer' for a mechanism that handles that.
212-- terminating. See 'withTracer' for a mechanism that handles that.
200−startTracer :: (TraceEvent -> Process ()) -> Process ProcessId
213+startTracer :: (MxEvent -> Process ()) -> Process ProcessId
201startTracer handler = do
214startTracer handler = do
202 withRegisteredTracer $ \pid -> do
215 withRegisteredTracer $ \pid -> do
203 node <- processNode <$> ask
216 node <- processNode <$> ask
@@ -209,7 +222,7 @@ startTracer handler = do
209-- disable tracing thereafter, before giving the result (or exception
222-- disable tracing thereafter, before giving the result (or exception
210-- in case of failure).
223-- in case of failure).
211withTracer :: forall a.
224withTracer :: forall a.
212− (TraceEvent -> Process ())
225+ (MxEvent -> Process ())
213 -> Process a
226 -> Process a
214 -> Process (Either SomeException a)
227 -> Process (Either SomeException a)
215withTracer handler proc = do
228withTracer handler proc = do
@@ -224,7 +237,7 @@ withTracer handler proc = do
224 Nothing -> return ()
237 Nothing -> return ()
225 Just _ -> do
238 Just _ -> do
226 ref <- monitor tracer
239 ref <- monitor tracer
227− send tracer TraceEvDisable
240+ send tracer MxTraceDisable
228 receiveWait [
241 receiveWait [
229 matchIf (\(ProcessMonitorNotification ref' _ _) -> ref == ref')
242 matchIf (\(ProcessMonitorNotification ref' _ _) -> ref == ref')
230 (\_ -> return ())
243 (\_ -> return ())
@@ -241,12 +254,12 @@ withFlags flags proc = do
241 finally (setTraceFlags flags >> try proc)
254 finally (setTraceFlags flags >> try proc)
242 (setTraceFlags oldFlags)
255 (setTraceFlags oldFlags)
243
256
244−traceProxy :: ProcessId -> (TraceEvent -> Process ()) -> Process ()
257+traceProxy :: ProcessId -> (MxEvent -> Process ()) -> Process ()
245traceProxy pid act = do
258traceProxy pid act = do
246− proxy pid $ \(ev :: TraceEvent) ->
259+ proxy pid $ \(ev :: MxEvent) ->
247 case ev of
260 case ev of
248− (TraceEvTakeover _) -> return False
261+ (MxTraceTakeover _) -> return False
249− TraceEvDisable -> die "disabled"
262+ MxTraceDisable -> die "disabled"
250 _ -> act ev >> return True
263 _ -> act ev >> return True
251
264
252-- | Stops a user supplied tracer started with 'startTracer'.
265-- | Stops a user supplied tracer started with 'startTracer'.
@@ -272,5 +285,4 @@ stopTracer =
272 basePid <- whereis "tracer.initial"
285 basePid <- whereis "tracer.initial"
273 case basePid == (Just pid) of
286 case basePid == (Just pid) of
274 True -> return ()
287 True -> return ()
275− False -> send pid TraceEvDisable
288+ False -> send pid MxTraceDisable
276−
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+68−16
@@ -1,5 +1,8 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveFunctor #-}
3+{-# LANGUAGE BangPatterns #-}
4+{-# LANGUAGE MagicHash, UnboxedTuples, PatternGuards, ScopedTypeVariables, RankNTypes #-}
1-- | Concurrent queue for single reader, single writer
5-- | Concurrent queue for single reader, single writer
2−{-# LANGUAGE MagicHash, UnboxedTuples, PatternGuards #-}
3module Control.Distributed.Process.Internal.CQueue
6module Control.Distributed.Process.Internal.CQueue
4 ( CQueue
7 ( CQueue
5 , BlockSpec(..)
8 , BlockSpec(..)
@@ -9,6 +12,7 @@ module Control.Distributed.Process.Internal.CQueue
9 , enqueueSTM
12 , enqueueSTM
10 , dequeue
13 , dequeue
11 , mkWeakCQueue
14 , mkWeakCQueue
15+ , queueSize
12 ) where
16 ) where
13
17
14import Prelude hiding (length, reverse)
18import Prelude hiding (length, reverse)
@@ -16,10 +20,14 @@ import Control.Concurrent.STM
16 ( atomically
20 ( atomically
17 , STM
21 , STM
18 , TChan
22 , TChan
23+ , TVar
24+ , modifyTVar'
19 , tryReadTChan
25 , tryReadTChan
20 , newTChan
26 , newTChan
27+ , newTVarIO
21 , writeTChan
28 , writeTChan
22 , readTChan
29 , readTChan
30+ , readTVarIO
23 , orElse
31 , orElse
24 , retry
32 , retry
25 )
33 )
@@ -37,39 +45,56 @@ import Control.Distributed.Process.Internal.StrictList
37 , append
45 , append
38 )
46 )
39import Data.Maybe (fromJust)
47import Data.Maybe (fromJust)
48+import Data.Traversable (traverse)
40import GHC.MVar (MVar(MVar))
49import GHC.MVar (MVar(MVar))
41−import GHC.IO (IO(IO))
50+import GHC.IO (IO(IO), unIO)
42−import GHC.Prim (mkWeak#)
51+import GHC.Exts (mkWeak#)
43import GHC.Weak (Weak(Weak))
52import GHC.Weak (Weak(Weak))
44
53
45-- We use a TCHan rather than a Chan so that we have a non-blocking read
54-- We use a TCHan rather than a Chan so that we have a non-blocking read
46data CQueue a = CQueue (StrictMVar (StrictList a)) -- Arrived
55data CQueue a = CQueue (StrictMVar (StrictList a)) -- Arrived
47 (TChan a) -- Incoming
56 (TChan a) -- Incoming
57+ (TVar Int) -- Queue size
48
58
49newCQueue :: IO (CQueue a)
59newCQueue :: IO (CQueue a)
50−newCQueue = CQueue <$> newMVar Nil <*> atomically newTChan
60+newCQueue = CQueue <$> newMVar Nil <*> atomically newTChan <*> newTVarIO 0
51
61
52-- | Enqueue an element
62-- | Enqueue an element
53--
63--
54-- Enqueue is strict.
64-- Enqueue is strict.
55enqueue :: CQueue a -> a -> IO ()
65enqueue :: CQueue a -> a -> IO ()
56−enqueue (CQueue _arrived incoming) !a = atomically $ writeTChan incoming a
66+enqueue c !a = atomically (enqueueSTM c a)
57
67
58-- | Variant of enqueue for use in the STM monad.
68-- | Variant of enqueue for use in the STM monad.
59enqueueSTM :: CQueue a -> a -> STM ()
69enqueueSTM :: CQueue a -> a -> STM ()
60−enqueueSTM (CQueue _arrived incoming) !a = writeTChan incoming a
70+enqueueSTM (CQueue _arrived incoming size) !a = do
71+ writeTChan incoming a
72+ modifyTVar' size succ
61
73
62data BlockSpec =
74data BlockSpec =
63 NonBlocking
75 NonBlocking
64 | Blocking
76 | Blocking
65 | Timeout Int
77 | Timeout Int
66
78
79+-- Match operations
80+--
81+-- They can be either a message match or a channel match.
67data MatchOn m a
82data MatchOn m a
68 = MatchMsg (m -> Maybe a)
83 = MatchMsg (m -> Maybe a)
69 | MatchChan (STM a)
84 | MatchChan (STM a)
85+ deriving (Functor)
70
86
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.
71type MatchChunks m a = [Either [m -> Maybe a] [STM a]]
92type MatchChunks m a = [Either [m -> Maybe a] [STM a]]
72
93
94+-- Splits a list of matches into chunks.
95+--
96+-- > concatMap (either (map MatchMsg) (map MatchChan)) . chunkMatches == id
97+--
73chunkMatches :: [MatchOn m a] -> MatchChunks m a
98chunkMatches :: [MatchOn m a] -> MatchChunks m a
74chunkMatches [] = []
99chunkMatches [] = []
75chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest
100chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest
@@ -77,6 +102,7 @@ chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest
77chunkMatches (MatchChan r : ms) = Right (r : chk) : chunkMatches rest
102chunkMatches (MatchChan r : ms) = Right (r : chk) : chunkMatches rest
78 where (chk, rest) = spanMatchChan ms
103 where (chk, rest) = spanMatchChan ms
79
104
105+-- | @spanMatchMsg = first (map (\(MatchMsg x) -> x)) . span isMatchMsg@
80spanMatchMsg :: [MatchOn m a] -> ([m -> Maybe a], [MatchOn m a])
106spanMatchMsg :: [MatchOn m a] -> ([m -> Maybe a], [MatchOn m a])
81spanMatchMsg [] = ([],[])
107spanMatchMsg [] = ([],[])
82spanMatchMsg (m : ms)
108spanMatchMsg (m : ms)
@@ -84,6 +110,7 @@ spanMatchMsg (m : ms)
84 | otherwise = ([], m:ms)
110 | otherwise = ([], m:ms)
85 where !(msgs,rest) = spanMatchMsg ms
111 where !(msgs,rest) = spanMatchMsg ms
86
112
113+-- | @spanMatchMsg = first (map (\(MatchChan x) -> x)) . span isMatchChan@
87spanMatchChan :: [MatchOn m a] -> ([STM a], [MatchOn m a])
114spanMatchChan :: [MatchOn m a] -> ([STM a], [MatchOn m a])
88spanMatchChan [] = ([],[])
115spanMatchChan [] = ([],[])
89spanMatchChan (m : ms)
116spanMatchChan (m : ms)
@@ -100,7 +127,7 @@ dequeue :: forall m a.
100 -> BlockSpec -- ^ Blocking behaviour
127 -> BlockSpec -- ^ Blocking behaviour
101 -> [MatchOn m a] -- ^ List of matches
128 -> [MatchOn m a] -- ^ List of matches
102 -> IO (Maybe a) -- ^ 'Nothing' only on timeout
129 -> IO (Maybe a) -- ^ 'Nothing' only on timeout
103−dequeue (CQueue arrived incoming) blockSpec matchons =
130+dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJust $
104 case blockSpec of
131 case blockSpec of
105 Timeout n -> timeout n $ fmap fromJust run
132 Timeout n -> timeout n $ fmap fromJust run
106 _other ->
133 _other ->
@@ -112,9 +139,17 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
112 -- no onException needed
139 -- no onException needed
113 _other -> run
140 _other -> run
114 where
141 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+
115 chunks = chunkMatches matchons
150 chunks = chunkMatches matchons
116
151
117− run = mask_ $ do
152+ run = do
118 arr <- takeMVar arrived
153 arr <- takeMVar arrived
119 let grabNew xs = do
154 let grabNew xs = do
120 r <- atomically $ tryReadTChan incoming
155 r <- atomically $ tryReadTChan incoming
@@ -124,17 +159,25 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
124 arr' <- grabNew arr
159 arr' <- grabNew arr
125 goCheck chunks arr'
160 goCheck chunks arr'
126
161
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))
127 waitChans ports on_block =
166 waitChans ports on_block =
128− foldr orElse on_block (map (fmap Just) ports)
167+ foldr orElse on_block (map (fmap (Just . Left)) ports)
129
168
130 --
169 --
131 -- First check the MatchChunks against the messages already in the
170 -- First check the MatchChunks against the messages already in the
132 -- mailbox. For channel matches, we do a non-blocking check at
171 -- mailbox. For channel matches, we do a non-blocking check at
133 -- this point.
172 -- this point.
134 --
173 --
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+ --
135 goCheck :: MatchChunks m a
178 goCheck :: MatchChunks m a
136 -> StrictList m -- messages to check, in this order
179 -> StrictList m -- messages to check, in this order
137− -> IO (Maybe a)
180+ -> IO (Maybe (Either a a))
138
181
139 goCheck [] old = goWait old
182 goCheck [] old = goWait old
140
183
@@ -151,7 +194,7 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
151 -- of passing around restore and setting up exception handlers is
194 -- of passing around restore and setting up exception handlers is
152 -- high. So just don't use expensive matchIfs!
195 -- high. So just don't use expensive matchIfs!
153 case checkArrived matches old of
196 case checkArrived matches old of
154− (old', Just r) -> returnOld old' (Just r)
197+ (old', Just r) -> returnOld old' (Just (Right r))
155 (old', Nothing) -> goCheck rest old'
198 (old', Nothing) -> goCheck rest old'
156 -- use the result list, which is now left-biased
199 -- use the result list, which is now left-biased
157
200
@@ -180,6 +223,7 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
180 -- Contents of 'arrived' from now on is (old ++ new), and
223 -- Contents of 'arrived' from now on is (old ++ new), and
181 -- messages that arrive are snocced onto new.
224 -- messages that arrive are snocced onto new.
182 --
225 --
226+ goWait :: StrictList m -> IO (Maybe (Either a a))
183 goWait old = do
227 goWait old = do
184 r <- waitIncoming `onException` putMVar arrived old
228 r <- waitIncoming `onException` putMVar arrived old
185 case r of
229 case r of
@@ -197,7 +241,7 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
197 --
241 --
198 -- Right => message arrived on a channel first
242 -- Right => message arrived on a channel first
199 --
243 --
200− Right a -> returnOld old (Just a)
244+ Right a -> returnOld old (Just (Left a))
201
245
202 --
246 --
203 -- A message arrived in the process inbox; check the MatchChunks for
247 -- A message arrived in the process inbox; check the MatchChunks for
@@ -206,7 +250,7 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
206 goCheck1 :: MatchChunks m a
250 goCheck1 :: MatchChunks m a
207 -> m -- single message to check
251 -> m -- single message to check
208 -> StrictList m -- old messages we have already checked
252 -> StrictList m -- old messages we have already checked
209− -> IO (Maybe a)
253+ -> IO (Maybe (Either a a))
210
254
211 goCheck1 [] m old = goWait (Snoc old m)
255 goCheck1 [] m old = goWait (Snoc old m)
212
256
@@ -219,16 +263,17 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
219 goCheck1 (Left matches : rest) m old = do
263 goCheck1 (Left matches : rest) m old = do
220 case checkMatches matches m of
264 case checkMatches matches m of
221 Nothing -> goCheck1 rest m old
265 Nothing -> goCheck1 rest m old
222− Just p -> returnOld old (Just p)
266+ Just p -> returnOld old (Just (Right p))
223
267
224 -- a common pattern for putting back the arrived queue at the end
268 -- a common pattern for putting back the arrived queue at the end
225− returnOld :: StrictList m -> Maybe a -> IO (Maybe a)
269+ returnOld :: StrictList m -> Maybe (Either a a) -> IO (Maybe (Either a a))
226 returnOld old r = do putMVar arrived old; return r
270 returnOld old r = do putMVar arrived old; return r
227
271
228 -- as a side-effect, this left-biases the list
272 -- as a side-effect, this left-biases the list
229 checkArrived :: [m -> Maybe a] -> StrictList m -> (StrictList m, Maybe a)
273 checkArrived :: [m -> Maybe a] -> StrictList m -> (StrictList m, Maybe a)
230 checkArrived matches list = go list Nil
274 checkArrived matches list = go list Nil
231 where
275 where
276+ -- @go xs ys@ searches for a message match in @append xs ys@
232 go Nil Nil = (Nil, Nothing)
277 go Nil Nil = (Nil, Nothing)
233 go Nil r = go r Nil
278 go Nil r = go r Nil
234 go (Append xs ys) tl = go xs (append ys tl)
279 go (Append xs ys) tl = go xs (append ys tl)
@@ -244,5 +289,12 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
244
289
245-- | Weak reference to a CQueue
290-- | Weak reference to a CQueue
246mkWeakCQueue :: CQueue a -> IO () -> IO (Weak (CQueue a))
291mkWeakCQueue :: CQueue a -> IO () -> IO (Weak (CQueue a))
247−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
248 case mkWeak# m# m f s of (# s1, w #) -> (# s1, Weak w #)
296 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+27−8
@@ -1,3 +1,5 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+{-# LANGUAGE RankNTypes #-}
1module Control.Distributed.Process.Internal.Closure.BuiltIn
3module Control.Distributed.Process.Internal.Closure.BuiltIn
2 ( -- * Remote table
4 ( -- * Remote table
3 remoteTable
5 remoteTable
@@ -6,6 +8,8 @@ module Control.Distributed.Process.Internal.Closure.BuiltIn
6 , sdictUnit
8 , sdictUnit
7 , sdictProcessId
9 , sdictProcessId
8 , sdictSendPort
10 , sdictSendPort
11+ , sdictStatic
12+ , sdictClosure
9 -- * Some static values
13 -- * Some static values
10 , sndStatic
14 , sndStatic
11 -- * The CP type and associated combinators
15 -- * The CP type and associated combinators
@@ -24,7 +28,7 @@ module Control.Distributed.Process.Internal.Closure.BuiltIn
24 , cpExpect
28 , cpExpect
25 , cpNewChan
29 , cpNewChan
26 -- * Support for some CH operations
30 -- * Support for some CH operations
27− , cpDelay
31+ , cpDelayed
28 , cpEnableTraceRemote
32 , cpEnableTraceRemote
29 ) where
33 ) where
30
34
@@ -48,6 +52,7 @@ import Control.Distributed.Static
48import Control.Distributed.Process.Serializable
52import Control.Distributed.Process.Serializable
49 ( SerializableDict(..)
53 ( SerializableDict(..)
50 , Serializable
54 , Serializable
55+ , TypeableDict(..)
51 )
56 )
52import Control.Distributed.Process.Internal.Types
57import Control.Distributed.Process.Internal.Types
53 ( Process
58 ( Process
@@ -80,6 +85,8 @@ remoteTable =
80 . registerStatic "$sdictUnit" (toDynamic (SerializableDict :: SerializableDict ()))
85 . registerStatic "$sdictUnit" (toDynamic (SerializableDict :: SerializableDict ()))
81 . registerStatic "$sdictProcessId" (toDynamic (SerializableDict :: SerializableDict ProcessId))
86 . registerStatic "$sdictProcessId" (toDynamic (SerializableDict :: SerializableDict ProcessId))
82 . registerStatic "$sdictSendPort_" (toDynamic (sdictSendPort_ :: SerializableDict ANY -> SerializableDict (SendPort ANY)))
87 . registerStatic "$sdictSendPort_" (toDynamic (sdictSendPort_ :: SerializableDict ANY -> SerializableDict (SendPort ANY)))
88+ . registerStatic "$sdictStatic" (toDynamic (sdictStatic_ :: TypeableDict ANY -> SerializableDict (Static ANY)))
89+ . registerStatic "$sdictClosure" (toDynamic (sdictClosure_ :: TypeableDict ANY -> SerializableDict (Closure ANY)))
83 . registerStatic "$returnProcess" (toDynamic (return :: ANY -> Process ANY))
90 . registerStatic "$returnProcess" (toDynamic (return :: ANY -> Process ANY))
84 . registerStatic "$seqProcess" (toDynamic ((>>) :: Process ANY1 -> Process ANY2 -> Process ANY2))
91 . registerStatic "$seqProcess" (toDynamic ((>>) :: Process ANY1 -> Process ANY2 -> Process ANY2))
85 . registerStatic "$bindProcess" (toDynamic ((>>=) :: Process ANY1 -> (ANY1 -> Process ANY2) -> Process ANY2))
92 . registerStatic "$bindProcess" (toDynamic ((>>=) :: Process ANY1 -> (ANY1 -> Process ANY2) -> Process ANY2))
@@ -100,6 +107,12 @@ remoteTable =
100 sdictSendPort_ :: forall a. SerializableDict a -> SerializableDict (SendPort a)
107 sdictSendPort_ :: forall a. SerializableDict a -> SerializableDict (SendPort a)
101 sdictSendPort_ SerializableDict = SerializableDict
108 sdictSendPort_ SerializableDict = SerializableDict
102
109
110+ sdictStatic_ :: forall a. TypeableDict a -> SerializableDict (Static a)
111+ sdictStatic_ TypeableDict = SerializableDict
112+
113+ sdictClosure_ :: forall a. TypeableDict a -> SerializableDict (Closure a)
114+ sdictClosure_ TypeableDict = SerializableDict
115+
103 sendDict :: forall a. SerializableDict a -> ProcessId -> a -> Process ()
116 sendDict :: forall a. SerializableDict a -> ProcessId -> a -> Process ()
104 sendDict SerializableDict = send
117 sendDict SerializableDict = send
105
118
@@ -142,6 +155,14 @@ sdictSendPort :: Typeable a
142 => Static (SerializableDict a) -> Static (SerializableDict (SendPort a))
155 => Static (SerializableDict a) -> Static (SerializableDict (SendPort a))
143sdictSendPort = staticApply (staticLabel "$sdictSendPort_")
156sdictSendPort = staticApply (staticLabel "$sdictSendPort_")
144
157
158+-- | Serialization dictionary for 'Static'.
159+sdictStatic :: Typeable a => Static (TypeableDict a) -> Static (SerializableDict (Static a))
160+sdictStatic = staticApply (staticLabel "$sdictStatic")
161+
162+-- | Serialization dictionary for 'Closure'.
163+sdictClosure :: Typeable a => Static (TypeableDict a) -> Static (SerializableDict (Closure a))
164+sdictClosure = staticApply (staticLabel "$sdictClosure")
165+
145--------------------------------------------------------------------------------
166--------------------------------------------------------------------------------
146-- Static values --
167-- Static values --
147--------------------------------------------------------------------------------
168--------------------------------------------------------------------------------
@@ -274,19 +295,20 @@ cpEnableTraceRemote =
274-- | @delay them p@ is a process that waits for a signal (a message of type @()@)
295-- | @delay them p@ is a process that waits for a signal (a message of type @()@)
275-- from 'them' (origin is not verified) before proceeding as @p@. In order to
296-- from 'them' (origin is not verified) before proceeding as @p@. In order to
276-- avoid waiting forever, @delay them p@ monitors 'them'. If it receives a
297-- avoid waiting forever, @delay them p@ monitors 'them'. If it receives a
277−-- monitor message instead it simply terminates.
298+-- monitor message instead, it proceeds as @p@ too.
278delay :: ProcessId -> Process () -> Process ()
299delay :: ProcessId -> Process () -> Process ()
279delay them p = do
300delay them p = do
280 ref <- monitor them
301 ref <- monitor them
281 let sameRef (ProcessMonitorNotification ref' _ _) = ref == ref'
302 let sameRef (ProcessMonitorNotification ref' _ _) = ref == ref'
282 receiveWait [
303 receiveWait [
283− match $ \() -> unmonitor ref >> p
304+ match $ \() -> unmonitor ref
284 , matchIf sameRef $ \_ -> return ()
305 , matchIf sameRef $ \_ -> return ()
285 ]
306 ]
307+ p
286
308
287-- | 'CP' version of 'delay'
309-- | 'CP' version of 'delay'
288−cpDelay :: ProcessId -> Closure (Process ()) -> Closure (Process ())
310+cpDelayed :: ProcessId -> Closure (Process ()) -> Closure (Process ())
289−cpDelay = closureApply . cpDelay'
311+cpDelayed = closureApply . cpDelay'
290 where
312 where
291 cpDelay' :: ProcessId -> Closure (Process () -> Process ())
313 cpDelay' :: ProcessId -> Closure (Process () -> Process ())
292 cpDelay' pid = closure decoder (encode pid)
314 cpDelay' pid = closure decoder (encode pid)
@@ -296,6 +318,3 @@ cpDelay = closureApply . cpDelay'
296
318
297 delayStatic :: Static (ProcessId -> Process () -> Process ())
319 delayStatic :: Static (ProcessId -> Process () -> Process ())
298 delayStatic = staticLabel "$delay"
320 delayStatic = staticLabel "$delay"
299−
300−
301−
src/Control/Distributed/Process/Internal/Closure/Explicit.hsnew file+147
@@ -0,0 +1,147 @@
1+{-# LANGUAGE ScopedTypeVariables
2+ , MultiParamTypeClasses
3+ , FlexibleInstances
4+ , FunctionalDependencies
5+ , FlexibleContexts
6+ , UndecidableInstances
7+ , KindSignatures
8+ , GADTs
9+ , EmptyDataDecls
10+ , DeriveDataTypeable #-}
11+module Control.Distributed.Process.Internal.Closure.Explicit
12+ (
13+ RemoteRegister
14+ , MkTDict(..)
15+ , mkStaticVal
16+ , mkClosureValSingle
17+ , mkClosureVal
18+ , call'
19+ ) where
20+
21+import Control.Distributed.Static
22+import Control.Distributed.Process.Serializable
23+import Control.Distributed.Process.Internal.Closure.BuiltIn
24+ ( -- Static dictionaries and associated operations
25+ staticDecode
26+ )
27+import Control.Distributed.Process
28+import Data.Rank1Dynamic
29+import Data.Rank1Typeable
30+import Data.Binary(encode,put,get,Binary)
31+import qualified Data.ByteString.Lazy as B
32+
33+-- | A RemoteRegister is a trasformer on a RemoteTable to register additional static values.
34+type RemoteRegister = RemoteTable -> RemoteTable
35+
36+-- | This takes an explicit name and a value, and produces both a static reference to the name and a RemoteRegister for it.
37+mkStaticVal :: Serializable a => String -> a -> (Static a, RemoteRegister)
38+mkStaticVal n v = (staticLabel n_s, registerStatic n_s (toDynamic v))
39+ where n_s = n
40+
41+class MkTDict a where
42+ mkTDict :: String -> a -> RemoteRegister
43+
44+instance (Serializable b) => MkTDict (Process b) where
45+ mkTDict _ _ = registerStatic (show (typeOf (undefined :: b)) ++ "__staticDict") (toDynamic (SerializableDict :: SerializableDict b))
46+
47+instance MkTDict a where
48+ mkTDict _ _ = id
49+
50+-- | This takes an explicit name, a function of arity one, and creates a creates a function yielding a closure and a remote register for it.
51+mkClosureValSingle :: forall a b. (Serializable a, Typeable b, MkTDict b) => String -> (a -> b) -> (a -> Closure b, RemoteRegister)
52+mkClosureValSingle n v = (c, registerStatic n_s (toDynamic v) .
53+ registerStatic n_sdict (toDynamic sdict) .
54+ mkTDict n_tdict (undefined :: b)
55+ ) where
56+ n_s = n
57+ n_sdict = n ++ "__sdict"
58+ n_tdict = n ++ "__tdict"
59+
60+ c = closure decoder . encode
61+
62+ decoder = (staticLabel n_s :: Static (a -> b)) `staticCompose` staticDecode (staticLabel n_sdict :: Static (SerializableDict a))
63+
64+ sdict :: (SerializableDict a)
65+ sdict = SerializableDict
66+
67+-- | This takes an explict name, a function of any arity, and creates a function yielding a closure and a remote register for it.
68+mkClosureVal :: forall func argTuple result closureFunction.
69+ (Curry (argTuple -> Closure result) closureFunction,
70+ MkTDict result,
71+ Uncurry HTrue argTuple func result,
72+ Typeable result, Serializable argTuple, IsFunction func HTrue) =>
73+ String -> func -> (closureFunction, RemoteRegister)
74+mkClosureVal n v = (curryFun c, rtable)
75+ where
76+ uv :: argTuple -> result
77+ uv = uncurry' reify v
78+
79+ n_s = n
80+ n_sdict = n ++ "__sdict"
81+ n_tdict = n ++ "__tdict"
82+
83+ c :: argTuple -> Closure result
84+ c = closure decoder . encode
85+
86+ decoder :: Static (B.ByteString -> result)
87+ decoder = (staticLabel n_s :: Static (argTuple -> result)) `staticCompose` staticDecode (staticLabel n_sdict :: Static (SerializableDict argTuple))
88+
89+ rtable = registerStatic n_s (toDynamic uv) .
90+ registerStatic n_sdict (toDynamic sdict) .
91+ mkTDict n_tdict (undefined :: result)
92+
93+
94+ sdict :: (SerializableDict argTuple)
95+ sdict = SerializableDict
96+
97+-- | Works just like standard call, but with a simpler signature.
98+call' :: forall a. Serializable a => NodeId -> Closure (Process a) -> Process a
99+call' = call (staticLabel $ (show $ typeOf $ (undefined :: a)) ++ "__staticDict")
100+
101+
102+data EndOfTuple deriving Typeable
103+instance Binary EndOfTuple where
104+ put _ = return ()
105+ get = return undefined
106+
107+-- This generic curry is straightforward
108+class Curry a b | a -> b where
109+ curryFun :: a -> b
110+
111+instance Curry ((a, EndOfTuple) -> b) (a -> b) where
112+ curryFun f = \x -> f (x,undefined)
113+
114+instance Curry (b -> c) r => Curry ((a,b) -> c) (a -> r) where
115+ curryFun f = \x -> curryFun (\y -> (f (x,y)))
116+
117+
118+-- This generic uncurry courtesy Andrea Vezzosi
119+data HTrue
120+data HFalse
121+data Fun :: * -> * -> * -> * where
122+ Done :: Fun EndOfTuple r r
123+ Moar :: Fun xs f r -> Fun (x,xs) (x -> f) r
124+
125+class Uncurry'' args func result | func -> args, func -> result, args result -> func where
126+ reify :: Fun args func result
127+
128+class Uncurry flag args func result | flag func -> args, flag func -> result, args result -> func where
129+ reify' :: flag -> Fun args func result
130+
131+instance Uncurry'' rest f r => Uncurry HTrue (a,rest) (a -> f) r where
132+ reify' _ = Moar reify
133+
134+instance Uncurry HFalse EndOfTuple a a where
135+ reify' _ = Done
136+
137+instance (IsFunction func b, Uncurry b args func result) => Uncurry'' args func result where
138+ reify = reify' (undefined :: b)
139+
140+uncurry' :: Fun args func result -> func -> args -> result
141+uncurry' Done r _ = r
142+uncurry' (Moar fun) f (x,xs) = uncurry' fun (f x) xs
143+
144+class IsFunction t b | t -> b
145+instance (b ~ HTrue) => IsFunction (a -> c) b
146+instance (b ~ HFalse) => IsFunction a b
147+
src/Control/Distributed/Process/Internal/Closure/TH.hs+24−8
@@ -1,5 +1,5 @@
1-- | Template Haskell support
1-- | Template Haskell support
2−{-# LANGUAGE TemplateHaskell #-}
2+{-# LANGUAGE TemplateHaskell, CPP #-}
3module Control.Distributed.Process.Internal.Closure.TH
3module Control.Distributed.Process.Internal.Closure.TH
4 ( -- * User-level API
4 ( -- * User-level API
5 remotable
5 remotable
@@ -29,7 +29,14 @@ import Language.Haskell.TH
29 , Type(AppT, ForallT, VarT, ArrowT)
29 , Type(AppT, ForallT, VarT, ArrowT)
30 , Info(VarI)
30 , Info(VarI)
31 , TyVarBndr(PlainTV, KindedTV)
31 , TyVarBndr(PlainTV, KindedTV)
32− , Pred(ClassP)
32+ , Pred
33+#if MIN_VERSION_template_haskell(2,10,0)
34+ , conT
35+ , appT
36+#else
37+ , classP
38+#endif
39+ , varT
33 -- Lifted constructors
40 -- Lifted constructors
34 -- .. Literals
41 -- .. Literals
35 , stringL
42 , stringL
@@ -244,16 +251,21 @@ generateStatic :: Name -> [TyVarBndr] -> Type -> Q [Dec]
244generateStatic n xs typ = do
251generateStatic n xs typ = do
245 staticTyp <- [t| Static |]
252 staticTyp <- [t| Static |]
246 sequence
253 sequence
247− [ sigD (staticName n) $
254+ [ sigD (staticName n) $ do
255+ txs <- sequence $ map typeable xs
248 return (ForallT xs
256 return (ForallT xs
249− (map typeable xs)
257+ txs
250− (staticTyp `AppT` typ)
258+ (staticTyp `AppT` typ))
251− )
252 , sfnD (staticName n) [| staticLabel $(showFQN n) |]
259 , sfnD (staticName n) [| staticLabel $(showFQN n) |]
253 ]
260 ]
254 where
261 where
255− typeable :: TyVarBndr -> Pred
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
256− typeable tv = ClassP (mkName "Typeable") [VarT (tyVarBndrName tv)]
267+ classP (mkName "Typeable") [varT (tyVarBndrName tv)]
268+#endif
257
269
258-- | Generate a serialization dictionary with name 'n' for type 'typ'
270-- | Generate a serialization dictionary with name 'n' for type 'typ'
259generateDict :: Name -> Type -> Q [Dec]
271generateDict :: Name -> Type -> Q [Dec]
@@ -291,7 +303,11 @@ getType :: Name -> Q (Name, Type)
291getType name = do
303getType name = do
292 info <- reify name
304 info <- reify name
293 case info of
305 case info of
306+#if MIN_VERSION_template_haskell(2,11,0)
307+ VarI origName typ _ -> return (origName, typ)
308+#else
294 VarI origName typ _ _ -> return (origName, typ)
309 VarI origName typ _ _ -> return (origName, typ)
310+#endif
295 _ -> fail $ show name ++ " not found"
311 _ -> fail $ show name ++ " not found"
296
312
297-- | Variation on 'funD' which takes a single expression to define the function
313-- | Variation on 'funD' which takes a single expression to define the function
src/Control/Distributed/Process/Internal/Messaging.hs+37−28
@@ -13,12 +13,13 @@ import Data.Binary (Binary, encode)
13import qualified Data.Map as Map (partitionWithKey, elems)
13import qualified Data.Map as Map (partitionWithKey, elems)
14import qualified Data.ByteString.Lazy as BSL (toChunks)
14import qualified Data.ByteString.Lazy as BSL (toChunks)
15import qualified Data.ByteString as BSS (ByteString)
15import qualified Data.ByteString as BSS (ByteString)
16−import Control.Distributed.Process.Internal.StrictMVar (withMVar, modifyMVar_)
17import Control.Distributed.Process.Serializable ()
16import Control.Distributed.Process.Serializable ()
18
17
18+import Control.Concurrent (forkIO)
19import Control.Concurrent.Chan (writeChan)
19import Control.Concurrent.Chan (writeChan)
20+import Control.Exception (mask_)
20import Control.Monad (unless)
21import Control.Monad (unless)
21−import Control.Monad.IO.Class (MonadIO, liftIO)
22+import Control.Monad.IO.Class (liftIO)
22import Control.Monad.Reader (ask)
23import Control.Monad.Reader (ask)
23import qualified Network.Transport as NT
24import qualified Network.Transport as NT
24 ( Connection
25 ( Connection
@@ -29,7 +30,10 @@ import qualified Network.Transport as NT
29 , close
30 , close
30 )
31 )
31import Control.Distributed.Process.Internal.Types
32import Control.Distributed.Process.Internal.Types
32− ( LocalNode(localState, localEndPoint, localCtrlChan)
33+ ( LocalNode(localEndPoint, localCtrlChan)
34+ , withValidLocalState
35+ , modifyValidLocalState
36+ , modifyValidLocalState_
33 , Identifier
37 , Identifier
34 , localConnections
38 , localConnections
35 , localConnectionBetween
39 , localConnectionBetween
@@ -50,6 +54,7 @@ import Control.Distributed.Process.Internal.Types
50 , Identifier(NodeIdentifier, ProcessIdentifier, SendPortIdentifier)
54 , Identifier(NodeIdentifier, ProcessIdentifier, SendPortIdentifier)
51 )
55 )
52import Control.Distributed.Process.Serializable (Serializable)
56import Control.Distributed.Process.Serializable (Serializable)
57+import Data.Foldable (forM_)
53
58
54--------------------------------------------------------------------------------
59--------------------------------------------------------------------------------
55-- Message sending --
60-- Message sending --
@@ -73,7 +78,7 @@ sendPayload node from to implicitReconnect payload = do
73 unless didSend $ do
78 unless didSend $ do
74 writeChan (localCtrlChan node) NCMsg
79 writeChan (localCtrlChan node) NCMsg
75 { ctrlMsgSender = to
80 { ctrlMsgSender = to
76− , ctrlMsgSignal = Died to DiedDisconnect
81+ , ctrlMsgSignal = Died (NodeIdentifier $ nodeOf to) DiedDisconnect
77 }
82 }
78
83
79sendBinary :: Binary a
84sendBinary :: Binary a
@@ -102,7 +107,7 @@ setupConnBetween :: LocalNode
102 -> ImplicitReconnect
107 -> ImplicitReconnect
103 -> IO (Maybe NT.Connection)
108 -> IO (Maybe NT.Connection)
104setupConnBetween node from to implicitReconnect = do
109setupConnBetween node from to implicitReconnect = do
105− mConn <- NT.connect endPoint
110+ mConn <- NT.connect (localEndPoint node)
106 (nodeAddress . nodeOf $ to)
111 (nodeAddress . nodeOf $ to)
107 NT.ReliableOrdered
112 NT.ReliableOrdered
108 NT.defaultConnectHints
113 NT.defaultConnectHints
@@ -113,14 +118,11 @@ setupConnBetween node from to implicitReconnect = do
113 Left _ ->
118 Left _ ->
114 return Nothing
119 return Nothing
115 Right () -> do
120 Right () -> do
116− modifyMVar_ nodeState $ return .
121+ modifyValidLocalState_ node $
117− (localConnectionBetween from to ^= Just (conn, implicitReconnect))
122+ return . (localConnectionBetween from to ^= Just (conn, implicitReconnect))
118 return $ Just conn
123 return $ Just conn
119 Left _ ->
124 Left _ ->
120 return Nothing
125 return Nothing
121− where
122− endPoint = localEndPoint node
123− nodeState = localState node
124
126
125connBetween :: LocalNode
127connBetween :: LocalNode
126 -> Identifier
128 -> Identifier
@@ -128,33 +130,37 @@ connBetween :: LocalNode
128 -> ImplicitReconnect
130 -> ImplicitReconnect
129 -> IO (Maybe NT.Connection)
131 -> IO (Maybe NT.Connection)
130connBetween node from to implicitReconnect = do
132connBetween node from to implicitReconnect = do
133+ mConn <- withValidLocalState node $
131− mConn <- withMVar nodeState $ return . (^. localConnectionBetween from to)
134+ return . (^. localConnectionBetween from to)
132 case mConn of
135 case mConn of
133 Just (conn, _) ->
136 Just (conn, _) ->
134 return $ Just conn
137 return $ Just conn
135 Nothing ->
138 Nothing ->
136 setupConnBetween node from to implicitReconnect
139 setupConnBetween node from to implicitReconnect
137− where
138− nodeState = localState node
139
140
140disconnect :: LocalNode -> Identifier -> Identifier -> IO ()
141disconnect :: LocalNode -> Identifier -> Identifier -> IO ()
141−disconnect node from to =
142+disconnect node from to = mask_ $ do
142− modifyMVar_ (localState node) $ \st ->
143+ mio <- modifyValidLocalState node $ \vst ->
143− case st ^. localConnectionBetween from to of
144+ case vst ^. localConnectionBetween from to of
144 Nothing ->
145 Nothing ->
145− return st
146+ return (vst, return ())
146 Just (conn, _) -> do
147 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
149
152
150closeImplicitReconnections :: LocalNode -> Identifier -> IO ()
153closeImplicitReconnections :: LocalNode -> Identifier -> IO ()
151−closeImplicitReconnections node to =
154+closeImplicitReconnections node to = mask_ $ do
152− modifyMVar_ (localState node) $ \st -> do
155+ mconns <- modifyValidLocalState node $ \vst -> do
153 let shouldClose (_, to') (_, WithImplicitReconnect) = to `impliesDeathOf` to'
156 let shouldClose (_, to') (_, WithImplicitReconnect) = to `impliesDeathOf` to'
154 shouldClose _ _ = False
157 shouldClose _ _ = False
158+ let (affected, unaffected) =
155− let (affected, unaffected) = Map.partitionWithKey shouldClose (st ^. localConnections)
159+ Map.partitionWithKey shouldClose (vst ^. localConnections)
160+ return ( localConnections ^= unaffected $ vst
161+ , map fst $ Map.elems affected
162+ )
156− mapM_ (NT.close . fst) (Map.elems affected)
163+ forM_ mconns $ forkIO . mapM_ NT.close
157− return (localConnections ^= unaffected $ st)
158
164
159-- | @a `impliesDeathOf` b@ is true if the death of @a@ (for instance, a node)
165-- | @a `impliesDeathOf` b@ is true if the death of @a@ (for instance, a node)
160-- implies the death of @b@ (for instance, a process on that node)
166-- implies the death of @b@ (for instance, a process on that node)
@@ -177,7 +183,11 @@ _ `impliesDeathOf` _ =
177 False
183 False
178
184
179
185
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.
181sendCtrlMsg :: Maybe NodeId -- ^ Nothing for the local node
191sendCtrlMsg :: Maybe NodeId -- ^ Nothing for the local node
182 -> ProcessSignal -- ^ Message to send
192 -> ProcessSignal -- ^ Message to send
183 -> Process ()
193 -> Process ()
@@ -188,11 +198,10 @@ sendCtrlMsg mNid signal = do
188 }
198 }
189 case mNid of
199 case mNid of
190 Nothing -> do
200 Nothing -> do
191− liftIO $ writeChan (localCtrlChan (processNode proc)) msg
201+ liftIO $ writeChan (localCtrlChan (processNode proc)) $! msg
192 Just nid ->
202 Just nid ->
193 liftIO $ sendBinary (processNode proc)
203 liftIO $ sendBinary (processNode proc)
194− (ProcessIdentifier (processId proc))
204+ (NodeIdentifier (processNodeId $ processId proc))
195 (NodeIdentifier nid)
205 (NodeIdentifier nid)
196 WithImplicitReconnect
206 WithImplicitReconnect
197 msg
207 msg
198−
src/Control/Distributed/Process/Internal/Primitives.hs+335−109
@@ -1,3 +1,9 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE RankNTypes #-}
3+{-# LANGUAGE DeriveDataTypeable #-}
4+{-# LANGUAGE DeriveFunctor #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+{-# LANGUAGE ExistentialQuantification #-}
1{-# LANGUAGE BangPatterns #-}
7{-# LANGUAGE BangPatterns #-}
2
8
3-- | Cloud Haskell primitives
9-- | Cloud Haskell primitives
@@ -7,6 +13,7 @@
7module Control.Distributed.Process.Internal.Primitives
13module Control.Distributed.Process.Internal.Primitives
8 ( -- * Basic messaging
14 ( -- * Basic messaging
9 send
15 send
16+ , usend
10 , expect
17 , expect
11 -- * Channels
18 -- * Channels
12 , newChan
19 , newChan
@@ -16,8 +23,10 @@ module Control.Distributed.Process.Internal.Primitives
16 , mergePortsRR
23 , mergePortsRR
17 -- * Unsafe messaging variants
24 -- * Unsafe messaging variants
18 , unsafeSend
25 , unsafeSend
26+ , unsafeUSend
19 , unsafeSendChan
27 , unsafeSendChan
20 , unsafeNSend
28 , unsafeNSend
29+ , unsafeNSendRemote
21 -- * Advanced messaging
30 -- * Advanced messaging
22 , Match
31 , Match
23 , receiveWait
32 , receiveWait
@@ -28,14 +37,19 @@ module Control.Distributed.Process.Internal.Primitives
28 , matchAny
37 , matchAny
29 , matchAnyIf
38 , matchAnyIf
30 , matchChan
39 , matchChan
40+ , matchSTM
31 , matchMessage
41 , matchMessage
32 , matchMessageIf
42 , matchMessageIf
33 , isEncoded
43 , isEncoded
34 , wrapMessage
44 , wrapMessage
45+ , unsafeWrapMessage
35 , unwrapMessage
46 , unwrapMessage
36 , handleMessage
47 , handleMessage
37 , handleMessageIf
48 , handleMessageIf
49+ , handleMessage_
50+ , handleMessageIf_
38 , forward
51 , forward
52+ , uforward
39 , delegate
53 , delegate
40 , relay
54 , relay
41 , proxy
55 , proxy
@@ -54,13 +68,19 @@ module Control.Distributed.Process.Internal.Primitives
54 , getSelfNode
68 , getSelfNode
55 , ProcessInfo(..)
69 , ProcessInfo(..)
56 , getProcessInfo
70 , getProcessInfo
71+ , NodeStats(..)
72+ , getNodeStats
73+ , getLocalNodeStats
57 -- * Monitoring and linking
74 -- * Monitoring and linking
58 , link
75 , link
59 , unlink
76 , unlink
60 , monitor
77 , monitor
61 , unmonitor
78 , unmonitor
79+ , unmonitorAsync
62 , withMonitor
80 , withMonitor
81+ , withMonitor_
63 -- * Logging
82 -- * Logging
83+ , SayMessage(..)
64 , say
84 , say
65 -- * Registry
85 -- * Registry
66 , register
86 , register
@@ -82,6 +102,7 @@ module Control.Distributed.Process.Internal.Primitives
82 , catches
102 , catches
83 , try
103 , try
84 , mask
104 , mask
105+ , mask_
85 , onException
106 , onException
86 , bracket
107 , bracket
87 , bracket_
108 , bracket_
@@ -107,17 +128,27 @@ module Control.Distributed.Process.Internal.Primitives
107import Prelude hiding (catch)
128import Prelude hiding (catch)
108#endif
129#endif
109
130
110−import Data.Binary (decode)
131+import Data.Binary (Binary(..), Put, Get, decode)
111−import Data.Time.Clock (getCurrentTime)
132+import Data.Time.Clock (getCurrentTime, UTCTime(..))
133+import Data.Time.Calendar (Day(..))
112import Data.Time.Format (formatTime)
134import Data.Time.Format (formatTime)
135+#if MIN_VERSION_time(1,5,0)
136+import Data.Time.Format (defaultTimeLocale)
137+#else
113import System.Locale (defaultTimeLocale)
138import System.Locale (defaultTimeLocale)
139+#endif
114import System.Timeout (timeout)
140import System.Timeout (timeout)
115−import Control.Monad (when)
141+import Control.Monad (when, void)
116import Control.Monad.Reader (ask)
142import Control.Monad.Reader (ask)
117−import Control.Monad.IO.Class (MonadIO, liftIO)
143+import Control.Monad.IO.Class (liftIO)
118−import Control.Applicative ((<$>))
144+import Control.Monad.Catch
119−import Control.Exception (Exception(..), throw, throwIO, SomeException)
145+ ( Exception
146+ , SomeException
147+ , throwM
148+ , fromException
149+ )
120−import qualified Control.Exception as Ex (catch, mask, try)
150+import qualified Control.Monad.Catch as Catch
151+import Control.Applicative
121import Control.Distributed.Process.Internal.StrictMVar
152import Control.Distributed.Process.Internal.StrictMVar
122 ( StrictMVar
153 ( StrictMVar
123 , modifyMVar
154 , modifyMVar
@@ -156,6 +187,8 @@ import Control.Distributed.Process.Internal.Types
156 , MonitorRef(..)
187 , MonitorRef(..)
157 , SpawnRef(..)
188 , SpawnRef(..)
158 , ProcessSignal(..)
189 , ProcessSignal(..)
190+ , NodeMonitorNotification(..)
191+ , ProcessMonitorNotification(..)
159 , monitorCounter
192 , monitorCounter
160 , spawnCounter
193 , spawnCounter
161 , SendPort(..)
194 , SendPort(..)
@@ -166,6 +199,7 @@ import Control.Distributed.Process.Internal.Types
166 , SendPortId(..)
199 , SendPortId(..)
167 , Identifier(..)
200 , Identifier(..)
168 , ProcessExitException(..)
201 , ProcessExitException(..)
202+ , DiedReason(..)
169 , DidUnmonitor(..)
203 , DidUnmonitor(..)
170 , DidUnlinkProcess(..)
204 , DidUnlinkProcess(..)
171 , DidUnlinkNode(..)
205 , DidUnlinkNode(..)
@@ -175,10 +209,10 @@ import Control.Distributed.Process.Internal.Types
175 , ProcessRegistrationException(..)
209 , ProcessRegistrationException(..)
176 , ProcessInfo(..)
210 , ProcessInfo(..)
177 , ProcessInfoNone(..)
211 , ProcessInfoNone(..)
212+ , NodeStats(..)
178 , isEncoded
213 , isEncoded
179 , createMessage
214 , createMessage
180 , createUnencodedMessage
215 , createUnencodedMessage
181− , runLocalProcess
182 , ImplicitReconnect( NoImplicitReconnect)
216 , ImplicitReconnect( NoImplicitReconnect)
183 , LocalProcessState
217 , LocalProcessState
184 , LocalSendPortId
218 , LocalSendPortId
@@ -191,15 +225,18 @@ import Control.Distributed.Process.Internal.Messaging
191 , disconnect
225 , disconnect
192 , sendCtrlMsg
226 , sendCtrlMsg
193 )
227 )
194−import Control.Distributed.Process.Internal.Trace.Types
228+import Control.Distributed.Process.Management.Internal.Types
229+ ( MxEvent(..)
230+ )
231+import Control.Distributed.Process.Management.Internal.Trace.Types
195 ( traceEvent
232 ( traceEvent
196− , TraceEvent(..)
197 )
233 )
198import Control.Distributed.Process.Internal.WeakTQueue
234import Control.Distributed.Process.Internal.WeakTQueue
199 ( newTQueueIO
235 ( newTQueueIO
200 , readTQueue
236 , readTQueue
201 , mkWeakTQueue
237 , mkWeakTQueue
202 )
238 )
239+import Prelude
203
240
204import Unsafe.Coerce
241import Unsafe.Coerce
205
242
@@ -217,17 +254,15 @@ send them msg = do
217 node = processNode proc
254 node = processNode proc
218 nodeId = localNodeId node
255 nodeId = localNodeId node
219 destNode = (processNodeId them) in do
256 destNode = (processNodeId them) in do
220− case destNode == nodeId of
221− True -> sendLocal them msg
222− False -> liftIO $ sendMessage (processNode proc)
223− (ProcessIdentifier (processId proc))
224− (ProcessIdentifier them)
225− NoImplicitReconnect
226− msg
227− -- We do not fire the trace event until after the sending is complete;
228− -- In the remote case, 'sendMessage' can block in the networking stack.
229 liftIO $ traceEvent (localEventBus node)
257 liftIO $ traceEvent (localEventBus node)
230− (TraceEvSent them us (createUnencodedMessage msg))
258+ (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
231
266
232-- | /Unsafe/ variant of 'send'. This function makes /no/ attempt to serialize
267-- | /Unsafe/ variant of 'send'. This function makes /no/ attempt to serialize
233-- and (in the case when the destination process resides on the same local
268-- and (in the case when the destination process resides on the same local
@@ -236,6 +271,34 @@ send them msg = do
236unsafeSend :: Serializable a => ProcessId -> a -> Process ()
271unsafeSend :: Serializable a => ProcessId -> a -> Process ()
237unsafeSend = Unsafe.send
272unsafeSend = Unsafe.send
238
273
274+-- | Send a message unreliably.
275+--
276+-- Unlike 'send', this function is insensitive to 'reconnect'. It will
277+-- try to send the message regardless of the history of connection failures
278+-- between the nodes.
279+--
280+-- Message passing with 'usend' is ordered for a given sender and receiver
281+-- if the messages arrive at all.
282+--
283+usend :: Serializable a => ProcessId -> a -> Process ()
284+usend them msg = do
285+ proc <- ask
286+ let there = processNodeId them
287+ let (us, node) = (processId proc, processNode proc)
288+ let msg' = wrapMessage msg
289+ liftIO $ traceEvent (localEventBus node) (MxSent them us msg')
290+ if localNodeId (processNode proc) == there
291+ then sendLocal them msg
292+ else sendCtrlMsg (Just there) $ UnreliableSend (processLocalId them)
293+ (createMessage msg)
294+
295+-- | /Unsafe/ variant of 'usend'. This function makes /no/ attempt to serialize
296+-- the message when the destination process resides on the same local
297+-- node. Therefore, a local receiver would need to be prepared to cope with any
298+-- errors resulting from evaluation of the message.
299+unsafeUSend :: Serializable a => ProcessId -> a -> Process ()
300+unsafeUSend = Unsafe.usend
301+
239-- | Wait for a message of a specific type
302-- | Wait for a message of a specific type
240expect :: forall a. Serializable a => Process a
303expect :: forall a. Serializable a => Process a
241expect = receiveWait [match return]
304expect = receiveWait [match return]
@@ -244,7 +307,13 @@ expect = receiveWait [match return]
244-- Channels --
307-- Channels --
245--------------------------------------------------------------------------------
308--------------------------------------------------------------------------------
246
309
247−-- | 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+--
248newChan :: Serializable a => Process (SendPort a, ReceivePort a)
317newChan :: Serializable a => Process (SendPort a, ReceivePort a)
249newChan = do
318newChan = do
250 proc <- ask
319 proc <- ask
@@ -272,13 +341,16 @@ newChan = do
272sendChan :: Serializable a => SendPort a -> a -> Process ()
341sendChan :: Serializable a => SendPort a -> a -> Process ()
273sendChan (SendPort cid) msg = do
342sendChan (SendPort cid) msg = do
274 proc <- ask
343 proc <- ask
275− let node = localNodeId (processNode proc)
344+ let node = processNode proc
345+ pid = processId proc
346+ us = localNodeId node
276− destNode = processNodeId (sendPortProcessId cid) in do
347+ them = processNodeId (sendPortProcessId cid) in do
348+ liftIO $ traceEvent (localEventBus node) (MxSentToPort pid cid $ wrapMessage msg)
277− case destNode == node of
349+ case them == us of
278 True -> sendChanLocal cid msg
350 True -> sendChanLocal cid msg
279 False -> do
351 False -> do
280− liftIO $ sendBinary (processNode proc)
352+ liftIO $ sendBinary node
281− (ProcessIdentifier (processId proc))
353+ (ProcessIdentifier pid)
282 (SendPortIdentifier cid)
354 (SendPortIdentifier cid)
283 NoImplicitReconnect
355 NoImplicitReconnect
284 msg
356 msg
@@ -333,13 +405,17 @@ mergePortsRR = \ps -> do
333
405
334-- | Opaque type used in 'receiveWait' and 'receiveTimeout'
406-- | Opaque type used in 'receiveWait' and 'receiveTimeout'
335newtype Match b = Match { unMatch :: MatchOn Message (Process b) }
407newtype Match b = Match { unMatch :: MatchOn Message (Process b) }
408+ deriving (Functor)
336
409
337-- | Test the matches in order against each message in the queue
410-- | Test the matches in order against each message in the queue
338receiveWait :: [Match b] -> Process b
411receiveWait :: [Match b] -> Process b
339receiveWait ms = do
412receiveWait ms = do
340 queue <- processQueue <$> ask
413 queue <- processQueue <$> ask
341− Just proc <- liftIO $ dequeue queue Blocking (map unMatch ms)
414+ mProc <- liftIO $ dequeue queue Blocking (map unMatch ms)
415+ case mProc of
342− proc
416+ Just proc' -> proc'
417+ Nothing -> die $ "System Invariant Violation: CQueue.hs returned `Nothing` "
418+ ++ "in the absence of a timeout value."
343
419
344-- | Like 'receiveWait' but with a timeout.
420-- | Like 'receiveWait' but with a timeout.
345--
421--
@@ -355,9 +431,26 @@ receiveTimeout t ms = do
355 Nothing -> return Nothing
431 Nothing -> return Nothing
356 Just proc -> Just <$> proc
432 Just proc -> Just <$> proc
357
433
434+-- | Match on a typed channel
358matchChan :: ReceivePort a -> (a -> Process b) -> Match b
435matchChan :: ReceivePort a -> (a -> Process b) -> Match b
359matchChan p fn = Match $ MatchChan (fmap fn (receiveSTM p))
436matchChan p fn = Match $ MatchChan (fmap fn (receiveSTM p))
360
437
438+-- | Match on an arbitrary STM action.
439+--
440+-- This rather unusaul /match primitive/ allows us to compose arbitrary STM
441+-- actions with checks against our process' mailbox and/or any typed channel
442+-- @ReceivePort@s we may hold.
443+--
444+-- This allows us to process multiple input streams /along with our mailbox/,
445+-- in just the same way that 'matchChan' supports checking both the mailbox
446+-- /and/ an arbitrary set of typed channels in one atomic transaction.
447+--
448+-- Note there are no ordering guarnatees with respect to these disparate input
449+-- sources.
450+--
451+matchSTM :: STM a -> (a -> Process b) -> Match b
452+matchSTM stm fn = Match $ MatchChan (fmap fn stm)
453+
361-- | Match against any message of the right type
454-- | Match against any message of the right type
362match :: forall a b. Serializable a => (a -> Process b) -> Match b
455match :: forall a b. Serializable a => (a -> Process b) -> Match b
363match = matchIf (const True)
456match = matchIf (const True)
@@ -401,22 +494,35 @@ forward msg them = do
401 us = processId proc
494 us = processId proc
402 nid = localNodeId node
495 nid = localNodeId node
403 destNode = (processNodeId them) in do
496 destNode = (processNodeId them) in do
497+ liftIO $ traceEvent (localEventBus node) (MxSent them us msg)
404− case destNode == nid of
498+ if destNode == nid
405− True -> sendCtrlMsg Nothing (LocalSend them msg)
499+ then sendCtrlMsg Nothing (LocalSend them msg)
406− False -> liftIO $ sendPayload (processNode proc)
500+ else liftIO $ sendPayload (processNode proc)
407− (ProcessIdentifier (processId proc))
501+ (ProcessIdentifier (processId proc))
408− (ProcessIdentifier them)
502+ (ProcessIdentifier them)
409− NoImplicitReconnect
503+ NoImplicitReconnect
410− (messageToPayload msg)
504+ (messageToPayload msg)
411− -- We do not fire the trace event until after the sending is complete;
412− -- In the remote case, 'sendMessage' can block in the networking stack.
413− liftIO $ traceEvent (localEventBus node)
414− (TraceEvSent them us msg)
415
505
506+-- | Forward a raw 'Message' to the given 'ProcessId'.
507+--
508+-- Unlike 'forward', this function is insensitive to 'reconnect'. It will
509+-- try to send the message regardless of the history of connection failures
510+-- between the nodes.
511+uforward :: Message -> ProcessId -> Process ()
512+uforward msg them = do
513+ proc <- ask
514+ let node = processNode proc
515+ us = processId proc
516+ nid = localNodeId node
517+ destNode = (processNodeId them) in do
518+ liftIO $ traceEvent (localEventBus node) (MxSent them us msg)
519+ if destNode == nid
520+ then sendCtrlMsg Nothing (LocalSend them msg)
521+ else sendCtrlMsg (Just destNode) $ UnreliableSend (processLocalId them) msg
416
522
417-- | Wrap a 'Serializable' value in a 'Message'. Note that 'Message's are
523-- | Wrap a 'Serializable' value in a 'Message'. Note that 'Message's are
418-- 'Serializable' - like the datum they contain - but also note, deserialising
524-- 'Serializable' - like the datum they contain - but also note, deserialising
419−-- a 'Message' will yield a 'Message', not the type within it! To obtain the
525+-- such a 'Message' will yield a 'Message', not the type within it! To obtain the
420-- wrapped datum, use 'unwrapMessage' or 'handleMessage' with a specific type.
526-- wrapped datum, use 'unwrapMessage' or 'handleMessage' with a specific type.
421--
527--
422-- @
528-- @
@@ -431,6 +537,11 @@ forward msg them = do
431wrapMessage :: Serializable a => a -> Message
537wrapMessage :: Serializable a => a -> Message
432wrapMessage = createUnencodedMessage -- see [note Serializable UnencodedMessage]
538wrapMessage = createUnencodedMessage -- see [note Serializable UnencodedMessage]
433
539
540+-- | This is the /unsafe/ variant of 'wrapMessage'. See
541+-- "Control.Distributed.Process.UnsafePrimitives" for details.
542+unsafeWrapMessage :: Serializable a => a -> Message
543+unsafeWrapMessage = Unsafe.wrapMessage
544+
434-- [note Serializable UnencodedMessage]
545-- [note Serializable UnencodedMessage]
435-- if we attempt to serialise an UnencodedMessage, it will be converted to an
546-- if we attempt to serialise an UnencodedMessage, it will be converted to an
436-- EncodedMessage first, so we're ok here. See 'messageToPayload' in
547-- EncodedMessage first, so we're ok here. See 'messageToPayload' in
@@ -474,6 +585,11 @@ handleMessage :: forall m a b. (Monad m, Serializable a)
474 => Message -> (a -> m b) -> m (Maybe b)
585 => Message -> (a -> m b) -> m (Maybe b)
475handleMessage msg proc = handleMessageIf msg (const True) proc
586handleMessage msg proc = handleMessageIf msg (const True) proc
476
587
588+-- | Conditionally handle a raw 'Message'.
589+-- If the predicate @(a -> Bool)@ evaluates to @True@, invokes the supplied
590+-- handler, other returns @Nothing@ to indicate failure. See 'handleMessage'
591+-- for further information about runtime type checking.
592+--
477handleMessageIf :: forall m a b . (Monad m, Serializable a)
593handleMessageIf :: forall m a b . (Monad m, Serializable a)
478 => Message
594 => Message
479 -> (a -> Bool)
595 -> (a -> Bool)
@@ -496,6 +612,22 @@ handleMessageIf msg c proc = do
496 decoded :: a -- note [decoding]
612 decoded :: a -- note [decoding]
497 !decoded = decode (messageEncoding msg)
613 !decoded = decode (messageEncoding msg)
498
614
615+-- | As 'handleMessage' but ignores result, which is useful if you don't
616+-- care whether or not the handler succeeded.
617+--
618+handleMessage_ :: forall m a . (Monad m, Serializable a)
619+ => Message -> (a -> m ()) -> m ()
620+handleMessage_ msg proc = handleMessageIf_ msg (const True) proc
621+
622+-- | Conditional version of 'handleMessage_'.
623+--
624+handleMessageIf_ :: forall m a . (Monad m, Serializable a)
625+ => Message
626+ -> (a -> Bool)
627+ -> (a -> m ())
628+ -> m ()
629+handleMessageIf_ msg c proc = handleMessageIf msg c proc >> return ()
630+
499-- | Match against an arbitrary message. 'matchAny' removes the first available
631-- | Match against an arbitrary message. 'matchAny' removes the first available
500-- message from the process mailbox. To handle arbitrary /raw/ messages once
632-- message from the process mailbox. To handle arbitrary /raw/ messages once
501-- removed from the mailbox, see 'handleMessage' and 'unwrapMessage'.
633-- removed from the mailbox, see 'handleMessage' and 'unwrapMessage'.
@@ -551,7 +683,7 @@ delegate pid p = do
551 ]
683 ]
552 delegate pid p
684 delegate pid p
553
685
554−-- | A straight relay that simply forwards all messages to the supplied pid.
686+-- | A straight relay that forwards all messages to the supplied pid.
555relay :: ProcessId -> Process ()
687relay :: ProcessId -> Process ()
556relay !pid = receiveWait [ matchAny (\m -> forward m pid) ] >> relay pid
688relay !pid = receiveWait [ matchAny (\m -> forward m pid) ] >> relay pid
557
689
@@ -583,7 +715,7 @@ instance Exception ProcessTerminationException
583
715
584-- | Terminate immediately (throws a ProcessTerminationException)
716-- | Terminate immediately (throws a ProcessTerminationException)
585terminate :: Process a
717terminate :: Process a
586−terminate = liftIO $ throwIO ProcessTerminationException
718+terminate = throwM ProcessTerminationException
587
719
588-- [Issue #110]
720-- [Issue #110]
589-- | Die immediately - throws a 'ProcessExitException' with the given @reason@.
721-- | Die immediately - throws a 'ProcessExitException' with the given @reason@.
@@ -593,7 +725,7 @@ die reason = do
593-- could be decoded by a handler passed to 'catchExit', re-thrown or even
725-- could be decoded by a handler passed to 'catchExit', re-thrown or even
594-- passed to another process via the tracing mechanism.
726-- passed to another process via the tracing mechanism.
595 pid <- getSelfPid
727 pid <- getSelfPid
596− liftIO $ throwIO (ProcessExitException pid (createMessage reason))
728+ throwM (ProcessExitException pid (createMessage reason))
597
729
598-- | Forceful request to kill a process. Where 'exit' provides an exception
730-- | Forceful request to kill a process. Where 'exit' provides an exception
599-- that can be caught and handled, 'kill' throws an unexposed exception type
731-- that can be caught and handled, 'kill' throws an unexposed exception type
@@ -626,12 +758,12 @@ catchExit :: forall a b . (Show a, Serializable a)
626 => Process b
758 => Process b
627 -> (ProcessId -> a -> Process b)
759 -> (ProcessId -> a -> Process b)
628 -> Process b
760 -> Process b
629−catchExit act exitHandler = catch act handleExit
761+catchExit act exitHandler = Catch.catch act handleExit
630 where
762 where
631 handleExit ex@(ProcessExitException from msg) =
763 handleExit ex@(ProcessExitException from msg) =
632 if messageFingerprint msg == fingerprint (undefined :: a)
764 if messageFingerprint msg == fingerprint (undefined :: a)
633 then exitHandler from decoded
765 then exitHandler from decoded
634− else liftIO $ throwIO ex
766+ else throwM ex
635 where
767 where
636 decoded :: a
768 decoded :: a
637 -- Make sure the value is fully decoded so that we don't hang to
769 -- Make sure the value is fully decoded so that we don't hang to
@@ -650,12 +782,12 @@ catchExit act exitHandler = catch act handleExit
650catchesExit :: Process b
782catchesExit :: Process b
651 -> [(ProcessId -> Message -> (Process (Maybe b)))]
783 -> [(ProcessId -> Message -> (Process (Maybe b)))]
652 -> Process b
784 -> Process b
653−catchesExit act handlers = catch act ((flip handleExit) handlers)
785+catchesExit act handlers = Catch.catch act ((flip handleExit) handlers)
654 where
786 where
655 handleExit :: ProcessExitException
787 handleExit :: ProcessExitException
656 -> [(ProcessId -> Message -> Process (Maybe b))]
788 -> [(ProcessId -> Message -> Process (Maybe b))]
657 -> Process b
789 -> Process b
658− handleExit ex [] = liftIO $ throwIO ex
790+ handleExit ex [] = throwM ex
659 handleExit ex@(ProcessExitException from msg) (h:hs) = do
791 handleExit ex@(ProcessExitException from msg) (h:hs) = do
660 r <- h from msg
792 r <- h from msg
661 case r of
793 case r of
@@ -670,6 +802,31 @@ getSelfPid = processId <$> ask
670getSelfNode :: Process NodeId
802getSelfNode :: Process NodeId
671getSelfNode = localNodeId . processNode <$> ask
803getSelfNode = localNodeId . processNode <$> ask
672
804
805+
806+-- | Get statistics about the specified node
807+getNodeStats :: NodeId -> Process (Either DiedReason NodeStats)
808+getNodeStats nid = do
809+ selfNode <- getSelfNode
810+ if nid == selfNode
811+ then Right `fmap` getLocalNodeStats -- optimisation
812+ else getNodeStatsRemote selfNode
813+ where
814+ getNodeStatsRemote :: NodeId -> Process (Either DiedReason NodeStats)
815+ getNodeStatsRemote selfNode = do
816+ sendCtrlMsg (Just nid) $ GetNodeStats selfNode
817+ bracket (monitorNode nid) unmonitor $ \mRef ->
818+ receiveWait [ match (\(stats :: NodeStats) -> return $ Right stats)
819+ , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef)
820+ (\(NodeMonitorNotification _ _ dr) -> return $ Left dr)
821+ ]
822+
823+-- | Get statistics about our local node
824+getLocalNodeStats :: Process NodeStats
825+getLocalNodeStats = do
826+ self <- getSelfNode
827+ sendCtrlMsg Nothing $ GetNodeStats self
828+ receiveWait [ match (\(stats :: NodeStats) -> return stats) ]
829+
673-- | Get information about the specified process
830-- | Get information about the specified process
674getProcessInfo :: ProcessId -> Process (Maybe ProcessInfo)
831getProcessInfo :: ProcessId -> Process (Maybe ProcessInfo)
675getProcessInfo pid =
832getProcessInfo pid =
@@ -739,12 +896,19 @@ monitor = monitor' . ProcessIdentifier
739-- @withMonitor@ returns, there might still be unreceived monitor
896-- @withMonitor@ returns, there might still be unreceived monitor
740-- messages in the queue.
897-- messages in the queue.
741--
898--
742−withMonitor :: ProcessId -> Process a -> Process a
899+withMonitor :: ProcessId -> (MonitorRef -> Process a) -> Process a
743−withMonitor pid code = bracket (monitor pid) unmonitor (\_ -> code)
900+withMonitor pid = bracket (monitor pid) unmonitor
744− -- unmonitor blocks waiting for the response, so there's a possibility
901+
745− -- that an exception might interrupt withMonitor before the unmonitor
902+-- | Establishes temporary monitoring of another process.
746− -- has completed. I think that's better than making the unmonitor
903+--
747− -- uninterruptible.
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
748
912
749-- | Remove a link
913-- | Remove a link
750--
914--
@@ -782,12 +946,21 @@ unlinkPort sport = do
782-- | Remove a monitor
946-- | Remove a monitor
783--
947--
784-- This has the same synchronous/asynchronous nature as 'unlink'.
948-- This has the same synchronous/asynchronous nature as 'unlink'.
949+--
950+-- ProcessMonitorNotification messages for the given MonitorRef are removed from
951+-- the mailbox.
785unmonitor :: MonitorRef -> Process ()
952unmonitor :: MonitorRef -> Process ()
786unmonitor ref = do
953unmonitor ref = do
787 unmonitorAsync ref
954 unmonitorAsync ref
788 receiveWait [ matchIf (\(DidUnmonitor ref') -> ref' == ref)
955 receiveWait [ matchIf (\(DidUnmonitor ref') -> ref' == ref)
789 (\_ -> return ())
956 (\_ -> return ())
790 ]
957 ]
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+ ]
791
964
792--------------------------------------------------------------------------------
965--------------------------------------------------------------------------------
793-- Exception handling --
966-- Exception handling --
@@ -795,50 +968,43 @@ unmonitor ref = do
795
968
796-- | Lift 'Control.Exception.catch'
969-- | Lift 'Control.Exception.catch'
797catch :: Exception e => Process a -> (e -> Process a) -> Process a
970catch :: Exception e => Process a -> (e -> Process a) -> Process a
798−catch p h = do
971+catch = Catch.catch
799− lproc <- ask
972+{-# DEPRECATED catch "Use Control.Monad.Catch.catch instead" #-}
800− liftIO $ Ex.catch (runLocalProcess lproc p) (runLocalProcess lproc . h)
801
973
802-- | Lift 'Control.Exception.try'
974-- | Lift 'Control.Exception.try'
803try :: Exception e => Process a -> Process (Either e a)
975try :: Exception e => Process a -> Process (Either e a)
804−try p = do
976+try = Catch.try
805− lproc <- ask
977+{-# DEPRECATED try "Use Control.Monad.Catch.try instead" #-}
806− liftIO $ Ex.try (runLocalProcess lproc p)
807
978
808-- | Lift 'Control.Exception.mask'
979-- | Lift 'Control.Exception.mask'
809mask :: ((forall a. Process a -> Process a) -> Process b) -> Process b
980mask :: ((forall a. Process a -> Process a) -> Process b) -> Process b
810−mask p = do
981+mask = Catch.mask
811− lproc <- ask
982+{-# DEPRECATED mask "Use Control.Monad.Catch.mask_ instead" #-}
812− liftIO $ Ex.mask $ \restore ->
983+
813− runLocalProcess lproc (p (liftRestore restore))
984+-- | Lift 'Control.Exception.mask_'
814− where
815− liftRestore :: (forall a. IO a -> IO a)
816− -> (forall a. Process a -> Process a)
985+mask_ :: Process a -> Process a
817− liftRestore restoreIO = \p2 -> do
986+mask_ = Catch.mask_
818− ourLocalProc <- ask
987+{-# DEPRECATED mask_ "Use Control.Monad.Catch.mask_ instead" #-}
819− liftIO $ restoreIO $ runLocalProcess ourLocalProc p2
820
988
821-- | Lift 'Control.Exception.onException'
989-- | Lift 'Control.Exception.onException'
822onException :: Process a -> Process b -> Process a
990onException :: Process a -> Process b -> Process a
823−onException p what = p `catch` \e -> do _ <- what
991+onException = Catch.onException
824− liftIO $ throwIO (e :: SomeException)
992+{-# DEPRECATED onException "Use Control.Monad.Catch.onException instead" #-}
825
993
826-- | Lift 'Control.Exception.bracket'
994-- | Lift 'Control.Exception.bracket'
827bracket :: Process a -> (a -> Process b) -> (a -> Process c) -> Process c
995bracket :: Process a -> (a -> Process b) -> (a -> Process c) -> Process c
828−bracket before after thing =
996+bracket = Catch.bracket
829− mask $ \restore -> do
997+{-# DEPRECATED bracket "Use Control.Monad.Catch.bracket instead" #-}
830− a <- before
831− r <- restore (thing a) `onException` after a
832− _ <- after a
833− return r
834
998
835-- | Lift 'Control.Exception.bracket_'
999-- | Lift 'Control.Exception.bracket_'
836bracket_ :: Process a -> Process b -> Process c -> Process c
1000bracket_ :: Process a -> Process b -> Process c -> Process c
837−bracket_ before after thing = bracket before (const after) (const thing)
1001+bracket_ = Catch.bracket_
1002+{-# DEPRECATED bracket_ "Use Control.Monad.Catch.bracket_ instead" #-}
838
1003
839-- | Lift 'Control.Exception.finally'
1004-- | Lift 'Control.Exception.finally'
840finally :: Process a -> Process b -> Process a
1005finally :: Process a -> Process b -> Process a
841−finally a sequel = bracket_ (return ()) sequel a
1006+finally = Catch.finally
1007+{-# DEPRECATED finally "Use Control.Monad.Catch.finally instead" #-}
842
1008
843-- | You need this when using 'catches'
1009-- | You need this when using 'catches'
844data Handler a = forall e . Exception e => Handler (e -> Process a)
1010data Handler a = forall e . Exception e => Handler (e -> Process a)
@@ -848,10 +1014,10 @@ instance Functor Handler where
848
1014
849-- | Lift 'Control.Exception.catches'
1015-- | Lift 'Control.Exception.catches'
850catches :: Process a -> [Handler a] -> Process a
1016catches :: Process a -> [Handler a] -> Process a
851−catches proc handlers = proc `catch` catchesHandler handlers
1017+catches proc handlers = proc `Catch.catch` catchesHandler handlers
852
1018
853catchesHandler :: [Handler a] -> SomeException -> Process a
1019catchesHandler :: [Handler a] -> SomeException -> Process a
854−catchesHandler handlers e = foldr tryHandler (throw e) handlers
1020+catchesHandler handlers e = foldr tryHandler (throwM e) handlers
855 where tryHandler (Handler handler) res
1021 where tryHandler (Handler handler) res
856 = case fromException e of
1022 = case fromException e of
857 Just e' -> handler e'
1023 Just e' -> handler e'
@@ -871,7 +1037,10 @@ expectTimeout n = receiveTimeout n [match return]
871spawnAsync :: NodeId -> Closure (Process ()) -> Process SpawnRef
1037spawnAsync :: NodeId -> Closure (Process ()) -> Process SpawnRef
872spawnAsync nid proc = do
1038spawnAsync nid proc = do
873 spawnRef <- getSpawnRef
1039 spawnRef <- getSpawnRef
1040+ node <- getSelfNode
1041+ if nid == node
1042+ then sendCtrlMsg Nothing $ Spawn proc spawnRef
874− sendCtrlMsg (Just nid) $ Spawn proc spawnRef
1043+ else sendCtrlMsg (Just nid) $ Spawn proc spawnRef
875 return spawnRef
1044 return spawnRef
876
1045
877-- | Monitor a node (asynchronous)
1046-- | Monitor a node (asynchronous)
@@ -917,27 +1086,60 @@ unlinkPortAsync (SendPort cid) =
917-- Logging --
1086-- Logging --
918--------------------------------------------------------------------------------
1087--------------------------------------------------------------------------------
919
1088
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+
920-- | Log a string
1124-- | Log a string
921--
1125--
922−-- @say message@ sends a message (time, pid of the current process, message)
1126+-- @say message@ sends a message of type 'SayMessage' with the current time and
923−-- to the process registered as 'logger'. By default, this process simply
1127+-- 'ProcessId' of the current process to the process registered as @logger@. By
924−-- sends the string to 'stderr'. Individual Cloud Haskell backends might
1128+-- default, this process simply sends the string to @stderr@. Individual Cloud
925−-- replace this with a different logger process, however.
1129+-- Haskell backends might replace this with a different logger process, however.
926say :: String -> Process ()
1130say :: String -> Process ()
927say string = do
1131say string = do
928 now <- liftIO getCurrentTime
1132 now <- liftIO getCurrentTime
929 us <- getSelfPid
1133 us <- getSelfPid
930− nsend "logger" (formatTime defaultTimeLocale "%c" now, us, string)
1134+ nsend "logger" (SayMessage now us string)
931
1135
932--------------------------------------------------------------------------------
1136--------------------------------------------------------------------------------
933-- Registry --
1137-- Registry --
934--------------------------------------------------------------------------------
1138--------------------------------------------------------------------------------
935
1139
936−-- | Register a process with the local registry (asynchronous).
1140+-- | Register a process with the local registry (synchronous). The name must not
937−-- This version will wait until a response is gotten from the
938−-- management process. The name must not already be registered.
939−-- The process need not be on this node.
1141+-- already be registered. The process need not be on this node. A bad
940−-- A bad registration will result in a 'ProcessRegistrationException'
1142+-- registration will result in a 'ProcessRegistrationException'
941--
1143--
942-- The process to be registered does not have to be local itself.
1144-- The process to be registered does not have to be local itself.
943register :: String -> ProcessId -> Process ()
1145register :: String -> ProcessId -> Process ()
@@ -952,9 +1154,10 @@ registerImpl :: Bool -> String -> ProcessId -> Process ()
952registerImpl force label pid = do
1154registerImpl force label pid = do
953 mynid <- getSelfNode
1155 mynid <- getSelfNode
954 sendCtrlMsg Nothing (Register label mynid (Just pid) force)
1156 sendCtrlMsg Nothing (Register label mynid (Just pid) force)
1157+ receiveWait
955− receiveWait [ matchIf (\(RegisterReply label' _) -> label == label')
1158+ [ matchIf (\(RegisterReply label' _ _) -> label == label')
956− (\(RegisterReply _ ok) -> handleRegistrationReply label ok)
1159+ (\(RegisterReply _ ok owner) -> handleRegistrationReply label ok owner)
957− ]
1160+ ]
958
1161
959-- | Register a process with a remote registry (asynchronous).
1162-- | Register a process with a remote registry (asynchronous).
960--
1163--
@@ -963,8 +1166,10 @@ registerImpl force label pid = do
963--
1166--
964-- See comments in 'whereisRemoteAsync'
1167-- See comments in 'whereisRemoteAsync'
965registerRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
1168registerRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
966−registerRemoteAsync nid label pid =
1169+registerRemoteAsync nid label pid = do
1170+ here <- getSelfNode
1171+ sendCtrlMsg (if nid == here then Nothing else Just nid)
967− sendCtrlMsg (Just nid) (Register label nid (Just pid) False)
1172+ (Register label nid (Just pid) False)
968
1173
969reregisterRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
1174reregisterRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
970reregisterRemoteAsync nid label pid =
1175reregisterRemoteAsync nid label pid =
@@ -977,16 +1182,17 @@ unregister :: String -> Process ()
977unregister label = do
1182unregister label = do
978 mynid <- getSelfNode
1183 mynid <- getSelfNode
979 sendCtrlMsg Nothing (Register label mynid Nothing False)
1184 sendCtrlMsg Nothing (Register label mynid Nothing False)
1185+ receiveWait
980− receiveWait [ matchIf (\(RegisterReply label' _) -> label == label')
1186+ [ matchIf (\(RegisterReply label' _ _) -> label == label')
981− (\(RegisterReply _ ok) -> handleRegistrationReply label ok)
1187+ (\(RegisterReply _ ok owner) -> handleRegistrationReply label ok owner)
982− ]
1188+ ]
983
1189
984-- | Deal with the result from an attempted registration or unregistration
1190-- | Deal with the result from an attempted registration or unregistration
985-- by throwing an exception if necessary
1191-- by throwing an exception if necessary
986−handleRegistrationReply :: String -> Bool -> Process ()
1192+handleRegistrationReply :: String -> Bool -> Maybe ProcessId -> Process ()
987−handleRegistrationReply label ok =
1193+handleRegistrationReply label ok owner =
988 when (not ok) $
1194 when (not ok) $
989− liftIO $ throwIO $ ProcessRegistrationException label
1195+ throwM $ ProcessRegistrationException label owner
990
1196
991-- | Remove a process from a remote registry (asynchronous).
1197-- | Remove a process from a remote registry (asynchronous).
992--
1198--
@@ -1015,13 +1221,18 @@ whereis label = do
1015-- use 'monitorNode' and take appropriate action when you receive a
1221-- use 'monitorNode' and take appropriate action when you receive a
1016-- 'NodeMonitorNotification').
1222-- 'NodeMonitorNotification').
1017whereisRemoteAsync :: NodeId -> String -> Process ()
1223whereisRemoteAsync :: NodeId -> String -> Process ()
1018−whereisRemoteAsync nid label =
1224+whereisRemoteAsync nid label = do
1225+ here <- getSelfNode
1019− sendCtrlMsg (Just nid) (WhereIs label)
1226+ sendCtrlMsg (if nid == here then Nothing else Just nid) (WhereIs label)
1020
1227
1021-- | Named send to a process in the local registry (asynchronous)
1228-- | Named send to a process in the local registry (asynchronous)
1022nsend :: Serializable a => String -> a -> Process ()
1229nsend :: Serializable a => String -> a -> Process ()
1023−nsend label 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')
1024− sendCtrlMsg Nothing (NamedSend label (createUnencodedMessage msg))
1235+ sendCtrlMsg Nothing (NamedSend label msg')
1025
1236
1026-- | Named send to a process in the local registry (asynchronous).
1237-- | Named send to a process in the local registry (asynchronous).
1027-- This function makes /no/ attempt to serialize and (in the case when the
1238-- This function makes /no/ attempt to serialize and (in the case when the
@@ -1032,8 +1243,23 @@ unsafeNSend = Unsafe.nsend
1032
1243
1033-- | Named send to a process in a remote registry (asynchronous)
1244-- | Named send to a process in a remote registry (asynchronous)
1034nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
1245nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
1035−nsendRemote nid label 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))
1036− sendCtrlMsg (Just nid) (NamedSend label (createMessage 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
1037
1263
1038--------------------------------------------------------------------------------
1264--------------------------------------------------------------------------------
1039-- Closures --
1265-- Closures --
src/Control/Distributed/Process/Internal/Spawn.hs+14−10
@@ -1,3 +1,4 @@
1+{-# LANGUAGE RankNTypes #-}
1module Control.Distributed.Process.Internal.Spawn
2module Control.Distributed.Process.Internal.Spawn
2 ( spawn
3 ( spawn
3 , spawnLink
4 , spawnLink
@@ -36,11 +37,13 @@ import Control.Distributed.Process.Internal.Closure.BuiltIn
36 , cpLink
37 , cpLink
37 , cpSend
38 , cpSend
38 , cpNewChan
39 , cpNewChan
39− , cpDelay
40+ , cpDelayed
41+ , returnCP
42+ , sdictUnit
40 )
43 )
41import Control.Distributed.Process.Internal.Primitives
44import Control.Distributed.Process.Internal.Primitives
42 ( -- Basic messaging
45 ( -- Basic messaging
43− send
46+ usend
44 , expect
47 , expect
45 , receiveWait
48 , receiveWait
46 , match
49 , match
@@ -64,11 +67,11 @@ spawn :: NodeId -> Closure (Process ()) -> Process ProcessId
64spawn nid proc = do
67spawn nid proc = do
65 us <- getSelfPid
68 us <- getSelfPid
66 mRef <- monitorNode nid
69 mRef <- monitorNode nid
67− sRef <- spawnAsync nid (cpDelay us proc)
70+ sRef <- spawnAsync nid (cpDelayed us proc)
68 receiveWait [
71 receiveWait [
69 matchIf (\(DidSpawn ref _) -> ref == sRef) $ \(DidSpawn _ pid) -> do
72 matchIf (\(DidSpawn ref _) -> ref == sRef) $ \(DidSpawn _ pid) -> do
70 unmonitor mRef
73 unmonitor mRef
71− send pid ()
74+ usend pid ()
72 return pid
75 return pid
73 , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef) $ \_ ->
76 , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef) $ \_ ->
74 return (nullProcessId nid)
77 return (nullProcessId nid)
@@ -108,12 +111,14 @@ call :: Serializable a
108 -> Process a
111 -> Process a
109call dict nid proc = do
112call dict nid proc = do
110 us <- getSelfPid
113 us <- getSelfPid
111− (pid, mRef) <- spawnMonitor nid (proc `bindCP` cpSend dict us)
114+ (pid, mRef) <- spawnMonitor nid (proc `bindCP`
112− -- We are guaranteed to receive the reply before the monitor notification
115+ cpSend dict us `seqCP`
113− -- (if a reply is sent at all)
116+ -- Delay so the process does not terminate
114− -- NOTE: This might not be true if we switch to unreliable delivery.
117+ -- before the response arrives.
118+ cpDelayed us (returnCP sdictUnit ())
119+ )
115 mResult <- receiveWait
120 mResult <- receiveWait
116− [ match (return . Right)
121+ [ match $ \a -> usend pid () >> return (Right a)
117 , matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)
122 , matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)
118 (\(ProcessMonitorNotification _ _ reason) -> return (Left reason))
123 (\(ProcessMonitorNotification _ _ reason) -> return (Left reason))
119 ]
124 ]
@@ -158,4 +163,3 @@ spawnChannel dict nid proc = do
158 (cpSend (sdictSendPort dict) pid `splitCP` proc)
163 (cpSend (sdictSendPort dict) pid `splitCP` proc)
159 `bindCP`
164 `bindCP`
160 (idCP `closureCompose` staticClosure sndStatic)
165 (idCP `closureCompose` staticClosure sndStatic)
161−
src/Control/Distributed/Process/Internal/StrictMVar.hs+37−5
@@ -1,33 +1,43 @@
1-- | Like Control.Concurrent.MVar.Strict but reduce to HNF, not NF
1-- | Like Control.Concurrent.MVar.Strict but reduce to HNF, not NF
2−{-# LANGUAGE MagicHash, UnboxedTuples #-}
2+{-# LANGUAGE CPP, MagicHash, UnboxedTuples #-}
3module Control.Distributed.Process.Internal.StrictMVar
3module Control.Distributed.Process.Internal.StrictMVar
4 ( StrictMVar(StrictMVar)
4 ( StrictMVar(StrictMVar)
5 , newEmptyMVar
5 , newEmptyMVar
6 , newMVar
6 , newMVar
7 , takeMVar
7 , takeMVar
8 , putMVar
8 , putMVar
9+ , readMVar
9 , withMVar
10 , withMVar
10 , modifyMVar_
11 , modifyMVar_
11 , modifyMVar
12 , modifyMVar
13+ , modifyMVarMasked
12 , mkWeakMVar
14 , mkWeakMVar
13 ) where
15 ) where
14
16
15import Control.Applicative ((<$>))
17import Control.Applicative ((<$>))
16import Control.Monad ((>=>))
18import Control.Monad ((>=>))
19+#if MIN_VERSION_base(4,6,0)
17import Control.Exception (evaluate)
20import Control.Exception (evaluate)
21+#else
22+import Control.Exception (evaluate, mask_, onException)
23+#endif
18import qualified Control.Concurrent.MVar as MVar
24import qualified Control.Concurrent.MVar as MVar
19 ( MVar
25 ( MVar
20 , newEmptyMVar
26 , newEmptyMVar
21 , newMVar
27 , newMVar
22 , takeMVar
28 , takeMVar
23 , putMVar
29 , putMVar
30+ , readMVar
24 , withMVar
31 , withMVar
25 , modifyMVar_
32 , modifyMVar_
26 , modifyMVar
33 , modifyMVar
34+#if MIN_VERSION_base(4,6,0)
35+ , modifyMVarMasked
36+#endif
27 )
37 )
28import GHC.MVar (MVar(MVar))
38import GHC.MVar (MVar(MVar))
29−import GHC.IO (IO(IO))
39+import GHC.IO (IO(IO), unIO)
30−import GHC.Prim (mkWeak#)
40+import GHC.Exts (mkWeak#)
31import GHC.Weak (Weak(Weak))
41import GHC.Weak (Weak(Weak))
32
42
33newtype StrictMVar a = StrictMVar (MVar.MVar a)
43newtype StrictMVar a = StrictMVar (MVar.MVar a)
@@ -44,6 +54,9 @@ takeMVar (StrictMVar v) = MVar.takeMVar v
44putMVar :: StrictMVar a -> a -> IO ()
54putMVar :: StrictMVar a -> a -> IO ()
45putMVar (StrictMVar v) x = evaluate x >> MVar.putMVar v x
55putMVar (StrictMVar v) x = evaluate x >> MVar.putMVar v x
46
56
57+readMVar :: StrictMVar a -> IO a
58+readMVar (StrictMVar v) = MVar.readMVar v
59+
47withMVar :: StrictMVar a -> (a -> IO b) -> IO b
60withMVar :: StrictMVar a -> (a -> IO b) -> IO b
48withMVar (StrictMVar v) = MVar.withMVar v
61withMVar (StrictMVar v) = MVar.withMVar v
49
62
@@ -56,6 +69,25 @@ modifyMVar (StrictMVar v) f = MVar.modifyMVar v (f >=> evaluateFst)
56 evaluateFst :: (a, b) -> IO (a, b)
69 evaluateFst :: (a, b) -> IO (a, b)
57 evaluateFst (x, y) = evaluate x >> return (x, y)
70 evaluateFst (x, y) = evaluate x >> return (x, y)
58
71
72+modifyMVarMasked :: StrictMVar a -> (a -> IO (a, b)) -> IO b
73+modifyMVarMasked (StrictMVar v) f =
74+#if MIN_VERSION_base(4,6,0)
75+ MVar.modifyMVarMasked v (f >=> evaluateFst)
76+#else
77+ mask_ $ do
78+ a <- MVar.takeMVar v
79+ (a',b) <- (f a >>= evaluate) `onException` MVar.putMVar v a
80+ MVar.putMVar v a'
81+ return b
82+#endif
83+ where
84+ evaluateFst :: (a, b) -> IO (a, b)
85+ evaluateFst (x, y) = evaluate x >> return (x, y)
86+
59mkWeakMVar :: StrictMVar a -> IO () -> IO (Weak (StrictMVar a))
87mkWeakMVar :: StrictMVar a -> IO () -> IO (Weak (StrictMVar a))
60−mkWeakMVar m@(StrictMVar (MVar m#)) f = IO $ \s ->
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
61− case mkWeak# m# m f s of (# s1, w #) -> (# s1, Weak w #)
92+ case mkWeak# m# q f s of (# s', w #) -> (# s', Weak w #)
93+#endif
src/Control/Distributed/Process/Internal/Trace/Types.hsdeleted−281
@@ -1,281 +0,0 @@
1−-- | Tracing/Debugging support - Types
2−module Control.Distributed.Process.Internal.Trace.Types
3− ( MxEventBus(..)
4− , TraceEvent(..)
5− , SetTrace(..)
6− , Addressable(..)
7− , TraceSubject(..)
8− , TraceFlags(..)
9− , TraceArg(..)
10− , TraceOk(..)
11− , traceLog
12− , traceLogFmt
13− , traceEvent
14− , traceMessage
15− , defaultTraceFlags
16− , enableTrace
17− , enableTraceSync
18− , disableTrace
19− , disableTraceSync
20− , getTraceFlags
21− , setTraceFlags
22− , setTraceFlagsSync
23− , getCurrentTraceClient
24− ) where
25−
26−import Control.Applicative ((<$>), (<*>))
27−import Control.Distributed.Process.Internal.CQueue (enqueue)
28−import Control.Distributed.Process.Internal.Types
29− ( MxEventBus(..)
30− , NodeId
31− , ProcessId
32− , DiedReason
33− , Message
34− , SendPort
35− , unsafeCreateUnencodedMessage
36− )
37−import Control.Distributed.Process.Management.Bus
38− ( publishEvent
39− )
40−import Control.Distributed.Process.Serializable
41−import Data.Binary
42−import Data.Foldable (forM_)
43−import Data.List (intersperse)
44−import Data.Set (Set)
45−import qualified Data.Set as Set (fromList)
46−import Data.Typeable
47−import System.Mem.Weak (deRefWeak)
48−import Network.Transport
49− ( ConnectionId
50− )
51−
52−--------------------------------------------------------------------------------
53−-- Types --
54−--------------------------------------------------------------------------------
55−
56−data SetTrace = TraceEnable !ProcessId | TraceDisable
57− deriving (Typeable, Eq, Show)
58−
59−-- | An event that is fired when something /interesting/ happens.
60−-- Trace events are forwarded to a trace listener process, of which
61−-- at most one can be registered per-node.
62−data TraceEvent =
63− TraceEvSpawned ProcessId
64− -- ^ fired whenever a local process is spawned
65− | TraceEvRegistered ProcessId String
66− -- ^ fired whenever a process/name is registered (locally)
67− | TraceEvUnRegistered ProcessId String
68− -- ^ fired whenever a process/name is unregistered (locally)
69− | TraceEvDied ProcessId DiedReason
70− -- ^ fired whenever a process dies
71− | TraceEvNodeDied NodeId DiedReason
72− -- ^ fired whenever a node /dies/ (i.e., the connection is broken/disconnected)
73− | TraceEvSent ProcessId ProcessId Message
74− -- ^ fired whenever a message is sent from a local process
75− | TraceEvReceived ProcessId Message
76− -- ^ fired whenever a message is received by a local process
77− | TraceEvConnected ConnectionId
78− -- ^ fired when a network-transport connection is first established
79− | TraceEvDisconnected ConnectionId
80− -- ^ fired when a network-transport connection is broken/disconnected
81− | TraceEvUser Message
82− -- ^ a user defined trace event (see 'traceMessage')
83− | TraceEvLog String
84− -- ^ a /logging/ event - used for debugging purposes only
85− | TraceEvTakeover ProcessId
86− -- ^ notifies a trace listener that all subsequent traces will be sent to /pid/
87− | TraceEvDisable
88− -- ^ notifies a trace listener that it has been disabled/removed
89− deriving (Typeable, Show)
90−
91−class Addressable a where
92− resolveToPid :: a -> Maybe ProcessId
93−
94−instance Addressable TraceEvent where
95− resolveToPid (TraceEvSpawned p) = Just p
96− resolveToPid (TraceEvDied p _) = Just p
97− resolveToPid (TraceEvSent _ p _) = Just p
98− resolveToPid (TraceEvReceived p _) = Just p
99− resolveToPid _ = Nothing
100−
101−-- | Defines which processes will be traced by a given 'TraceFlag',
102−-- either by name, or @ProcessId@. Choosing @TraceAll@ is /by far/
103−-- the most efficient approach, as the tracer process therefore
104−-- avoids deciding whether or not a trace event is viable.
105−--
106−data TraceSubject =
107− TraceAll -- enable tracing for all running processes
108− | TraceProcs !(Set ProcessId) -- enable tracing for a set of processes
109− | TraceNames !(Set String) -- enable tracing for a set of named/registered processes
110− deriving (Show)
111−
112−-- | Defines /what/ will be traced. Flags that control tracing of
113−-- @Process@ events, take a 'TraceSubject' controlling which processes
114−-- should generate trace events in the target process.
115−data TraceFlags = TraceFlags {
116− traceSpawned :: !(Maybe TraceSubject) -- filter process spawned tracing
117− , traceDied :: !(Maybe TraceSubject) -- filter process died tracing
118− , traceRegistered :: !(Maybe TraceSubject) -- filter process registration tracing
119− , traceUnregistered :: !(Maybe TraceSubject) -- filter process un-registration
120− , traceSend :: !(Maybe TraceSubject) -- filter process/message tracing by sender
121− , traceRecv :: !(Maybe TraceSubject) -- filter process/message tracing by receiver
122− , traceNodes :: !Bool -- enable node status trace events
123− , traceConnections :: !Bool -- enable connection status trace events
124− } deriving (Typeable, Show)
125−
126−defaultTraceFlags :: TraceFlags
127−defaultTraceFlags =
128− TraceFlags {
129− traceSpawned = Nothing
130− , traceDied = Nothing
131− , traceRegistered = Nothing
132− , traceUnregistered = Nothing
133− , traceSend = Nothing
134− , traceRecv = Nothing
135− , traceNodes = False
136− , traceConnections = False
137− }
138−
139−data TraceArg =
140− TraceStr String
141− | forall a. (Show a) => Trace a
142−
143−-- | A generic 'ok' response from the trace coordinator.
144−data TraceOk = TraceOk
145− deriving (Typeable)
146−
147−--------------------------------------------------------------------------------
148−-- Internal/Common API --
149−--------------------------------------------------------------------------------
150−
151−traceLog :: MxEventBus -> String -> IO ()
152−traceLog tr s = publishEvent tr (unsafeCreateUnencodedMessage $ TraceEvLog s)
153−
154−traceLogFmt :: MxEventBus
155− -> String
156− -> [TraceArg]
157− -> IO ()
158−traceLogFmt t d ls =
159− traceLog t $ concat (intersperse d (map toS ls))
160− where toS :: TraceArg -> String
161− toS (TraceStr s) = s
162− toS (Trace a) = show a
163−
164−traceEvent :: MxEventBus -> TraceEvent -> IO ()
165−traceEvent tr ev = publishEvent tr (unsafeCreateUnencodedMessage ev)
166−
167−traceMessage :: Serializable m => MxEventBus -> m -> IO ()
168−traceMessage tr msg = traceEvent tr (TraceEvUser (unsafeCreateUnencodedMessage msg))
169−
170−enableTrace :: MxEventBus -> ProcessId -> IO ()
171−enableTrace t p =
172− publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)),
173− (TraceEnable p)))
174−
175−enableTraceSync :: MxEventBus -> SendPort TraceOk -> ProcessId -> IO ()
176−enableTraceSync t s p =
177− publishEvent t (unsafeCreateUnencodedMessage (Just s, TraceEnable p))
178−
179−disableTrace :: MxEventBus -> IO ()
180−disableTrace t =
181− publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)),
182− TraceDisable))
183−
184−disableTraceSync :: MxEventBus -> SendPort TraceOk -> IO ()
185−disableTraceSync t s =
186− publishEvent t (unsafeCreateUnencodedMessage ((Just s), TraceDisable))
187−
188−setTraceFlags :: MxEventBus -> TraceFlags -> IO ()
189−setTraceFlags t f =
190− publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)), f))
191−
192−setTraceFlagsSync :: MxEventBus -> SendPort TraceOk -> TraceFlags -> IO ()
193−setTraceFlagsSync t s f =
194− publishEvent t (unsafeCreateUnencodedMessage ((Just s), f))
195−
196−getTraceFlags :: MxEventBus -> SendPort TraceFlags -> IO ()
197−getTraceFlags t s = publishEvent t (unsafeCreateUnencodedMessage s)
198−
199−getCurrentTraceClient :: MxEventBus -> SendPort (Maybe ProcessId) -> IO ()
200−getCurrentTraceClient t s = publishEvent t (unsafeCreateUnencodedMessage s)
201−
202−class Traceable a where
203− uod :: [a] -> TraceSubject
204−
205−instance Traceable ProcessId where
206− uod = TraceProcs . Set.fromList
207−
208−instance Traceable String where
209− uod = TraceNames . Set.fromList
210−
211−--------------------------------------------------------------------------------
212−-- Binary Instances --
213−--------------------------------------------------------------------------------
214−
215−instance Binary SetTrace where
216− put (TraceEnable pid) = putWord8 1 >> put pid
217− put TraceDisable = putWord8 2
218−
219− get = do
220− header <- getWord8
221− case header of
222− 1 -> TraceEnable <$> get
223− 2 -> return TraceDisable
224− _ -> error "SetTrace.get - invalid header"
225−
226−instance Binary TraceEvent where
227− put (TraceEvSpawned pid) = putWord8 1 >> put pid
228− put (TraceEvRegistered pid str) = putWord8 2 >> put pid >> put str
229− put (TraceEvUnRegistered pid str) = putWord8 3 >> put pid >> put str
230− put (TraceEvDied pid res) = putWord8 4 >> put pid >> put res
231− put (TraceEvNodeDied nid res) = putWord8 5 >> put nid >> put res
232− put (TraceEvSent to from msg) = putWord8 6 >> put to >> put from >> put msg
233− put (TraceEvReceived pid msg) = putWord8 7 >> put pid >> put msg
234− put (TraceEvConnected cid) = putWord8 8 >> put cid
235− put (TraceEvDisconnected cid) = putWord8 9 >> put cid
236− put (TraceEvUser msg) = putWord8 10 >> put msg
237− put (TraceEvLog msg) = putWord8 11 >> put msg
238− put (TraceEvTakeover pid) = putWord8 12 >> put pid
239− put TraceEvDisable = putWord8 13
240−
241− get = do
242− header <- getWord8
243− case header of
244− 1 -> TraceEvSpawned <$> get
245− 2 -> TraceEvRegistered <$> get <*> get
246− 3 -> TraceEvUnRegistered <$> get <*> get
247− 4 -> TraceEvDied <$> get <*> get
248− 5 -> TraceEvNodeDied <$> get <*> get
249− 6 -> TraceEvSent <$> get <*> get <*> get
250− 7 -> TraceEvReceived <$> get <*> get
251− 8 -> TraceEvConnected <$> get
252− 9 -> TraceEvDisconnected <$> get
253− 10 -> TraceEvUser <$> get
254− 11 -> TraceEvLog <$> get
255− 12 -> TraceEvTakeover <$> get
256− 13 -> return TraceEvDisable
257− _ -> error "TraceEvent.get - invalid header"
258−
259−instance Binary TraceSubject where
260− put TraceAll = putWord8 1
261− put (TraceProcs pids) = putWord8 2 >> put pids
262− put (TraceNames names) = putWord8 3 >> put names
263−
264− get = do
265− header <- getWord8
266− case header of
267− 1 -> return TraceAll
268− 2 -> TraceProcs <$> get
269− 3 -> TraceNames <$> get
270− _ -> error "TraceSubject.get - invalid header"
271−
272−instance Binary TraceFlags where
273− put (TraceFlags s d g u m r n c) =
274− put s >> put d >> put g >> put u >> put m >> put r >> put n >> put c
275− get =
276− TraceFlags <$> get <*> get <*> get <*> get <*> get <*> get <*> get <*> get
277−
278−instance Binary TraceOk where
279− put _ = return ()
280− get = return TraceOk
281−
src/Control/Distributed/Process/Internal/Types.hs+193−37
@@ -1,4 +1,12 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE ExistentialQuantification #-}
3+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE GADTs #-}
1{-# LANGUAGE DeriveGeneric #-}
5{-# LANGUAGE DeriveGeneric #-}
6+{-# LANGUAGE CPP #-}
7+{-# LANGUAGE MultiParamTypeClasses #-}
8+{-# LANGUAGE TypeFamilies #-}
9+{-# LANGUAGE Rank2Types #-}
2
10
3-- | Types used throughout the Cloud Haskell framework
11-- | Types used throughout the Cloud Haskell framework
4--
12--
@@ -16,9 +24,14 @@ module Control.Distributed.Process.Internal.Types
16 , nullProcessId
24 , nullProcessId
17 -- * Local nodes and processes
25 -- * Local nodes and processes
18 , LocalNode(..)
26 , LocalNode(..)
27+ , LocalNodeState(..)
28+ , ValidLocalNodeState(..)
29+ , NodeClosedException(..)
30+ , withValidLocalState
31+ , modifyValidLocalState
32+ , modifyValidLocalState_
19 , Tracer(..)
33 , Tracer(..)
20 , MxEventBus(..)
34 , MxEventBus(..)
21− , LocalNodeState(..)
22 , LocalProcess(..)
35 , LocalProcess(..)
23 , LocalProcessState(..)
36 , LocalProcessState(..)
24 , Process(..)
37 , Process(..)
@@ -59,6 +72,7 @@ module Control.Distributed.Process.Internal.Types
59 , RegisterReply(..)
72 , RegisterReply(..)
60 , ProcessInfo(..)
73 , ProcessInfo(..)
61 , ProcessInfoNone(..)
74 , ProcessInfoNone(..)
75+ , NodeStats(..)
62 -- * Node controller internal data types
76 -- * Node controller internal data types
63 , NCMsg(..)
77 , NCMsg(..)
64 , ProcessSignal(..)
78 , ProcessSignal(..)
@@ -81,6 +95,7 @@ module Control.Distributed.Process.Internal.Types
81import System.Mem.Weak (Weak)
95import System.Mem.Weak (Weak)
82import Data.Map (Map)
96import Data.Map (Map)
83import Data.Int (Int32)
97import Data.Int (Int32)
98+import Data.Data (Data)
84import Data.Typeable (Typeable, typeOf)
99import Data.Typeable (Typeable, typeOf)
85import Data.Binary (Binary(put, get), putWord8, getWord8, encode)
100import Data.Binary (Binary(put, get), putWord8, getWord8, encode)
86import qualified Data.ByteString as BSS (ByteString, concat, copy)
101import qualified Data.ByteString as BSS (ByteString, concat, copy)
@@ -95,14 +110,17 @@ import qualified Data.ByteString.Lazy.Internal as BSL (ByteString(..))
95import Data.Accessor (Accessor, accessor)
110import Data.Accessor (Accessor, accessor)
96import Control.Category ((>>>))
111import Control.Category ((>>>))
97import Control.DeepSeq (NFData(..))
112import Control.DeepSeq (NFData(..))
98−import Control.Exception (Exception)
113+import Control.Exception (Exception, throwIO)
99import Control.Concurrent (ThreadId)
114import Control.Concurrent (ThreadId)
100import Control.Concurrent.Chan (Chan)
115import Control.Concurrent.Chan (Chan)
101import Control.Concurrent.STM (STM)
116import Control.Concurrent.STM (STM)
117+import Control.Concurrent.STM.TChan (TChan)
118+import Control.Monad.Catch (MonadThrow(..), MonadCatch(..), MonadMask(..))
102import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)
119import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)
103−import Control.Applicative (Applicative, Alternative, (<$>), (<*>))
120+import Control.Applicative
121+import Control.Monad.Fix (MonadFix)
104import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
122import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
105−import Control.Monad.IO.Class (MonadIO)
123+import Control.Monad.IO.Class (MonadIO(..))
106import Control.Distributed.Process.Serializable
124import Control.Distributed.Process.Serializable
107 ( Fingerprint
125 ( Fingerprint
108 , Serializable
126 , Serializable
@@ -113,13 +131,19 @@ import Control.Distributed.Process.Serializable
113 , showFingerprint
131 , showFingerprint
114 )
132 )
115import Control.Distributed.Process.Internal.CQueue (CQueue)
133import Control.Distributed.Process.Internal.CQueue (CQueue)
116−import Control.Distributed.Process.Internal.StrictMVar (StrictMVar)
134+import Control.Distributed.Process.Internal.StrictMVar
135+ ( StrictMVar
136+ , withMVar
137+ , modifyMVar
138+ , modifyMVar_
139+ )
117import Control.Distributed.Process.Internal.WeakTQueue (TQueue)
140import Control.Distributed.Process.Internal.WeakTQueue (TQueue)
118import Control.Distributed.Static (RemoteTable, Closure)
141import Control.Distributed.Static (RemoteTable, Closure)
119import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC (mapMaybe)
142import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC (mapMaybe)
120
143
121import Data.Hashable
144import Data.Hashable
122import GHC.Generics
145import GHC.Generics
146+import Prelude
123
147
124--------------------------------------------------------------------------------
148--------------------------------------------------------------------------------
125-- Node and process identifiers --
149-- Node and process identifiers --
@@ -127,9 +151,9 @@ import GHC.Generics
127
151
128-- | Node identifier
152-- | Node identifier
129newtype NodeId = NodeId { nodeAddress :: NT.EndPointAddress }
153newtype NodeId = NodeId { nodeAddress :: NT.EndPointAddress }
130− deriving (Eq, Ord, Typeable, Generic)
154+ deriving (Eq, Ord, Typeable, Data, Generic)
131instance Binary NodeId where
155instance Binary NodeId where
132−instance NFData NodeId
156+instance NFData NodeId where rnf (NodeId a) = rnf a `seq` ()
133instance Hashable NodeId where
157instance Hashable NodeId where
134instance Show NodeId where
158instance Show NodeId where
135 show (NodeId addr) = "nid://" ++ show addr
159 show (NodeId addr) = "nid://" ++ show addr
@@ -140,7 +164,7 @@ data LocalProcessId = LocalProcessId
140 { lpidUnique :: {-# UNPACK #-} !Int32
164 { lpidUnique :: {-# UNPACK #-} !Int32
141 , lpidCounter :: {-# UNPACK #-} !Int32
165 , lpidCounter :: {-# UNPACK #-} !Int32
142 }
166 }
143− deriving (Eq, Ord, Typeable, Generic, Show)
167+ deriving (Eq, Ord, Typeable, Data, Generic, Show)
144
168
145instance Hashable LocalProcessId where
169instance Hashable LocalProcessId where
146
170
@@ -151,10 +175,10 @@ data ProcessId = ProcessId
151 -- | Node-local identifier for the process
175 -- | Node-local identifier for the process
152 , processLocalId :: {-# UNPACK #-} !LocalProcessId
176 , processLocalId :: {-# UNPACK #-} !LocalProcessId
153 }
177 }
154− deriving (Eq, Ord, Typeable, Generic)
178+ deriving (Eq, Ord, Typeable, Data, Generic)
155
179
156instance Binary ProcessId where
180instance Binary ProcessId where
157−instance NFData ProcessId where
181+instance NFData ProcessId where rnf (ProcessId n _) = rnf n `seq` ()
158instance Hashable ProcessId where
182instance Hashable ProcessId where
159
183
160instance Show ProcessId where
184instance Show ProcessId where
@@ -169,6 +193,10 @@ data Identifier =
169 deriving (Eq, Ord, Generic)
193 deriving (Eq, Ord, Generic)
170
194
171instance Hashable Identifier where
195instance 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` ()
172
200
173instance Show Identifier where
201instance Show Identifier where
174 show (NodeIdentifier nid) = show nid
202 show (NodeIdentifier nid) = show nid
@@ -199,22 +227,29 @@ nullProcessId nid =
199-- Local nodes and processes --
227-- Local nodes and processes --
200--------------------------------------------------------------------------------
228--------------------------------------------------------------------------------
201
229
202−-- | Provides access to the trace controller.
230+-- | Provides access to the trace controller
203data Tracer = Tracer
231data Tracer = Tracer
204 {
232 {
233+ -- | Process id for the currently active trace handler
205 tracerPid :: !ProcessId
234 tracerPid :: !ProcessId
235+ -- | Weak reference to the tracer controller's mailbox
206 , weakQ :: !(Weak (CQueue Message))
236 , weakQ :: !(Weak (CQueue Message))
207 }
237 }
208
238
209−-- | Required for the system management and tracing facilities.
239+-- | Local system management event bus state
210data MxEventBus =
240data MxEventBus =
211 MxEventBusInitialising
241 MxEventBusInitialising
212 | MxEventBus
242 | MxEventBus
213 {
243 {
244+ -- | Process id of the management agent controller process
214 agent :: !ProcessId
245 agent :: !ProcessId
246+ -- | Configuration for the local trace controller
215 , tracer :: !Tracer
247 , tracer :: !Tracer
248+ -- | Weak reference to the management agent controller's mailbox
216 , evbuss :: !(Weak (CQueue Message))
249 , evbuss :: !(Weak (CQueue Message))
250+ -- | API for adding management agents to a running node
217− , mxNew :: !((Message -> Process ()) -> IO ProcessId)
251+ , mxNew :: !(((TChan Message, TChan Message) -> Process ()) -> IO ProcessId)
252+-- , mxReg :: !(StrictMVar (Map MxAgentId ))
218 }
253 }
219
254
220-- | Local nodes
255-- | Local nodes
@@ -227,7 +262,7 @@ data LocalNode = LocalNode
227 , localState :: !(StrictMVar LocalNodeState)
262 , localState :: !(StrictMVar LocalNodeState)
228 -- | Channel for the node controller
263 -- | Channel for the node controller
229 , localCtrlChan :: !(Chan NCMsg)
264 , localCtrlChan :: !(Chan NCMsg)
230− -- | Current active system debug/trace log
265+ -- | Internal management event bus
231 , localEventBus :: !MxEventBus
266 , localEventBus :: !MxEventBus
232 -- | Runtime lookup table for supporting closures
267 -- | Runtime lookup table for supporting closures
233 -- TODO: this should be part of the CH state, not the local endpoint state
268 -- TODO: this should be part of the CH state, not the local endpoint state
@@ -238,7 +273,11 @@ data ImplicitReconnect = WithImplicitReconnect | NoImplicitReconnect
238 deriving (Eq, Show)
273 deriving (Eq, Show)
239
274
240-- | Local node state
275-- | Local node state
241−data LocalNodeState = LocalNodeState
276+data LocalNodeState =
277+ LocalNodeValid {-# UNPACK #-} !ValidLocalNodeState
278+ | LocalNodeClosed
279+
280+data ValidLocalNodeState = ValidLocalNodeState
242 { -- | Processes running on this node
281 { -- | Processes running on this node
243 _localProcesses :: !(Map LocalProcessId LocalProcess)
282 _localProcesses :: !(Map LocalProcessId LocalProcess)
244 -- | Counter to assign PIDs
283 -- | Counter to assign PIDs
@@ -251,6 +290,40 @@ data LocalNodeState = LocalNodeState
251 (NT.Connection, ImplicitReconnect))
290 (NT.Connection, ImplicitReconnect))
252 }
291 }
253
292
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+
254-- | Processes running on our local node
327-- | Processes running on our local node
255data LocalProcess = LocalProcess
328data LocalProcess = LocalProcess
256 { processQueue :: !(CQueue Message)
329 { processQueue :: !(CQueue Message)
@@ -277,7 +350,50 @@ data LocalProcessState = LocalProcessState
277newtype Process a = Process {
350newtype Process a = Process {
278 unProcess :: ReaderT LocalProcess IO a
351 unProcess :: ReaderT LocalProcess IO a
279 }
352 }
353+ deriving ( Applicative
354+ , Functor
355+ , Monad
356+ , MonadFix
357+ , MonadIO
280− deriving (Functor, Monad, MonadIO, MonadReader LocalProcess, Typeable, Applicative)
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
281
397
282--------------------------------------------------------------------------------
398--------------------------------------------------------------------------------
283-- Typed channels --
399-- Typed channels --
@@ -302,6 +418,9 @@ instance Show SendPortId where
302 show (SendPortId (ProcessId (NodeId addr) (LocalProcessId _ plid)) clid)
418 show (SendPortId (ProcessId (NodeId addr) (LocalProcessId _ plid)) clid)
303 = "cid://" ++ show addr ++ ":" ++ show plid ++ ":" ++ show clid
419 = "cid://" ++ show addr ++ ":" ++ show plid ++ ":" ++ show clid
304
420
421+instance NFData SendPortId where
422+ rnf (SendPortId p _) = rnf p `seq` ()
423+
305data TypedChannel = forall a. Serializable a => TypedChannel (Weak (TQueue a))
424data TypedChannel = forall a. Serializable a => TypedChannel (Weak (TQueue a))
306
425
307-- | The send send of a typed channel (serializable)
426-- | The send send of a typed channel (serializable)
@@ -313,7 +432,7 @@ newtype SendPort a = SendPort {
313
432
314instance (Serializable a) => Binary (SendPort a) where
433instance (Serializable a) => Binary (SendPort a) where
315instance (Hashable a) => Hashable (SendPort a) where
434instance (Hashable a) => Hashable (SendPort a) where
316−instance (NFData a) => NFData (SendPort a) where
435+instance (NFData a) => NFData (SendPort a) where rnf (SendPort x) = x `seq` ()
317
436
318-- | The receive end of a typed channel (not serializable)
437-- | The receive end of a typed channel (not serializable)
319--
438--
@@ -351,6 +470,15 @@ data Message =
351 }
470 }
352 deriving (Typeable)
471 deriving (Typeable)
353
472
473+instance NFData Message where
474+#if MIN_VERSION_bytestring(0,10,0)
475+ rnf (EncodedMessage _ e) = rnf e `seq` ()
476+#else
477+ rnf (EncodedMessage _ e) = BSL.length e `seq` ()
478+#endif
479+ rnf (UnencodedMessage _ a) = e `seq` ()
480+ where e = BSL.length (encode a)
481+
354instance Show Message where
482instance Show Message where
355 show (EncodedMessage fp enc) = show enc ++ " :: " ++ showFingerprint fp []
483 show (EncodedMessage fp enc) = show enc ++ " :: " ++ showFingerprint fp []
356 show (UnencodedMessage _ uenc) = "[unencoded message] :: " ++ (show $ typeOf uenc)
484 show (UnencodedMessage _ uenc) = "[unencoded message] :: " ++ (show $ typeOf uenc)
@@ -377,7 +505,7 @@ createUnencodedMessage a =
377 let encoded = encode a in BSL.length encoded `seq` UnencodedMessage (fingerprint a) a
505 let encoded = encode a in BSL.length encoded `seq` UnencodedMessage (fingerprint a) a
378
506
379-- | Turn any serializable term into an unencodede/local message, without
507-- | Turn any serializable term into an unencodede/local message, without
380−-- evalutaing it!
508+-- evalutaing it! This is a dangerous business.
381unsafeCreateUnencodedMessage :: Serializable a => a -> Message
509unsafeCreateUnencodedMessage :: Serializable a => a -> Message
382unsafeCreateUnencodedMessage a = UnencodedMessage (fingerprint a) a
510unsafeCreateUnencodedMessage a = UnencodedMessage (fingerprint a) a
383
511
@@ -413,8 +541,11 @@ data MonitorRef = MonitorRef
413 -- | Unique to distinguish multiple monitor requests by the same process
541 -- | Unique to distinguish multiple monitor requests by the same process
414 , monitorRefCounter :: !Int32
542 , monitorRefCounter :: !Int32
415 }
543 }
416− deriving (Eq, Ord, Show, Generic)
544+ deriving (Eq, Ord, Show, Typeable, Generic)
417−instance Hashable MonitorRef where
545+instance Hashable MonitorRef
546+
547+instance NFData MonitorRef where
548+ rnf (MonitorRef i _) = rnf i `seq` ()
418
549
419-- | Message sent by process monitors
550-- | Message sent by process monitors
420data ProcessMonitorNotification =
551data ProcessMonitorNotification =
@@ -448,9 +579,11 @@ data PortLinkException =
448
579
449-- | Exception thrown when a process attempts to register
580-- | Exception thrown when a process attempts to register
450-- a process under an already-registered name or to
581-- a process under an already-registered name or to
451−-- 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.
452data ProcessRegistrationException =
585data ProcessRegistrationException =
453− ProcessRegistrationException !String
586+ ProcessRegistrationException !String !(Maybe ProcessId)
454 deriving (Typeable, Show)
587 deriving (Typeable, Show)
455
588
456-- | Internal exception thrown indirectly by 'exit'
589-- | Internal exception thrown indirectly by 'exit'
@@ -482,6 +615,10 @@ data DiedReason =
482 | DiedUnknownId
615 | DiedUnknownId
483 deriving (Show, Eq)
616 deriving (Show, Eq)
484
617
618+instance NFData DiedReason where
619+ rnf (DiedException s) = rnf s `seq` ()
620+ rnf x = x `seq` ()
621+
485-- | (Asynchronous) reply from unmonitor
622-- | (Asynchronous) reply from unmonitor
486newtype DidUnmonitor = DidUnmonitor MonitorRef
623newtype DidUnmonitor = DidUnmonitor MonitorRef
487 deriving (Typeable, Binary)
624 deriving (Typeable, Binary)
@@ -500,7 +637,7 @@ newtype DidUnlinkPort = DidUnlinkPort SendPortId
500
637
501-- | 'SpawnRef' are used to return pids of spawned processes
638-- | 'SpawnRef' are used to return pids of spawned processes
502newtype SpawnRef = SpawnRef Int32
639newtype SpawnRef = SpawnRef Int32
503− deriving (Show, Binary, Typeable, Eq)
640+ deriving (Show, Binary, Typeable, Eq, Ord)
504
641
505-- | (Asynchronius) reply from 'spawn'
642-- | (Asynchronius) reply from 'spawn'
506data DidSpawn = DidSpawn SpawnRef ProcessId
643data DidSpawn = DidSpawn SpawnRef ProcessId
@@ -511,14 +648,23 @@ data WhereIsReply = WhereIsReply String (Maybe ProcessId)
511 deriving (Show, Typeable)
648 deriving (Show, Typeable)
512
649
513-- | (Asynchronous) reply from 'register' and 'unregister'
650-- | (Asynchronous) reply from 'register' and 'unregister'
514−data RegisterReply = RegisterReply String Bool
651+data RegisterReply = RegisterReply String Bool (Maybe ProcessId)
515 deriving (Show, Typeable)
652 deriving (Show, Typeable)
516
653
654+data NodeStats = NodeStats {
655+ nodeStatsNode :: NodeId
656+ , nodeStatsRegisteredNames :: Int
657+ , nodeStatsMonitors :: Int
658+ , nodeStatsLinks :: Int
659+ , nodeStatsProcesses :: Int
660+ }
661+ deriving (Show, Eq, Typeable)
662+
517-- | Provide information about a running process
663-- | Provide information about a running process
518data ProcessInfo = ProcessInfo {
664data ProcessInfo = ProcessInfo {
519 infoNode :: NodeId
665 infoNode :: NodeId
520 , infoRegisteredNames :: [String]
666 , infoRegisteredNames :: [String]
521− , infoMessageQueueLength :: Maybe Int
667+ , infoMessageQueueLength :: Int
522 , infoMonitors :: [(ProcessId, MonitorRef)]
668 , infoMonitors :: [(ProcessId, MonitorRef)]
523 , infoLinks :: [ProcessId]
669 , infoLinks :: [ProcessId]
524 } deriving (Show, Eq, Typeable)
670 } deriving (Show, Eq, Typeable)
@@ -548,12 +694,14 @@ data ProcessSignal =
548 | WhereIs !String
694 | WhereIs !String
549 | Register !String !NodeId !(Maybe ProcessId) !Bool -- Use 'Nothing' to unregister, use True to force reregister
695 | Register !String !NodeId !(Maybe ProcessId) !Bool -- Use 'Nothing' to unregister, use True to force reregister
550 | NamedSend !String !Message
696 | NamedSend !String !Message
697+ | UnreliableSend !LocalProcessId !Message
551 | LocalSend !ProcessId !Message
698 | LocalSend !ProcessId !Message
552 | LocalPortSend !SendPortId !Message
699 | LocalPortSend !SendPortId !Message
553 | Kill !ProcessId !String
700 | Kill !ProcessId !String
554 | Exit !ProcessId !Message
701 | Exit !ProcessId !Message
555 | GetInfo !ProcessId
702 | GetInfo !ProcessId
556 | SigShutdown
703 | SigShutdown
704+ | GetNodeStats !NodeId
557 deriving Show
705 deriving Show
558
706
559--------------------------------------------------------------------------------
707--------------------------------------------------------------------------------
@@ -602,8 +750,10 @@ instance Binary ProcessSignal where
602 put (Exit pid reason) = putWord8 10 >> put pid >> put (messageToPayload reason)
750 put (Exit pid reason) = putWord8 10 >> put pid >> put (messageToPayload reason)
603 put (LocalSend to' msg) = putWord8 11 >> put to' >> put (messageToPayload msg)
751 put (LocalSend to' msg) = putWord8 11 >> put to' >> put (messageToPayload msg)
604 put (LocalPortSend sid msg) = putWord8 12 >> put sid >> put (messageToPayload msg)
752 put (LocalPortSend sid msg) = putWord8 12 >> put sid >> put (messageToPayload msg)
753+ put (UnreliableSend lpid msg) = putWord8 13 >> put lpid >> put (messageToPayload msg)
605 put (GetInfo about) = putWord8 30 >> put about
754 put (GetInfo about) = putWord8 30 >> put about
606 put (SigShutdown) = putWord8 31
755 put (SigShutdown) = putWord8 31
756+ put (GetNodeStats nid) = putWord8 32 >> put nid
607 get = do
757 get = do
608 header <- getWord8
758 header <- getWord8
609 case header of
759 case header of
@@ -620,8 +770,10 @@ instance Binary ProcessSignal where
620 10 -> Exit <$> get <*> (payloadToMessage <$> get)
770 10 -> Exit <$> get <*> (payloadToMessage <$> get)
621 11 -> LocalSend <$> get <*> (payloadToMessage <$> get)
771 11 -> LocalSend <$> get <*> (payloadToMessage <$> get)
622 12 -> LocalPortSend <$> get <*> (payloadToMessage <$> get)
772 12 -> LocalPortSend <$> get <*> (payloadToMessage <$> get)
773+ 13 -> UnreliableSend <$> get <*> (payloadToMessage <$> get)
623 30 -> GetInfo <$> get
774 30 -> GetInfo <$> get
624 31 -> return SigShutdown
775 31 -> return SigShutdown
776+ 32 -> GetNodeStats <$> get
625 _ -> fail "ProcessSignal.get: invalid"
777 _ -> fail "ProcessSignal.get: invalid"
626
778
627instance Binary DiedReason where
779instance Binary DiedReason where
@@ -648,9 +800,6 @@ instance Binary SendPortId where
648 put cid = put (sendPortProcessId cid) >> put (sendPortLocalId cid)
800 put cid = put (sendPortProcessId cid) >> put (sendPortLocalId cid)
649 get = SendPortId <$> get <*> get
801 get = SendPortId <$> get <*> get
650
802
651−instance NFData SendPortId where
652− rnf cid = (sendPortProcessId cid) `seq` (sendPortLocalId cid) `seq` ()
653−
654instance Binary Identifier where
803instance Binary Identifier where
655 put (ProcessIdentifier pid) = putWord8 0 >> put pid
804 put (ProcessIdentifier pid) = putWord8 0 >> put pid
656 put (NodeIdentifier nid) = putWord8 1 >> put nid
805 put (NodeIdentifier nid) = putWord8 1 >> put nid
@@ -668,8 +817,8 @@ instance Binary WhereIsReply where
668 get = WhereIsReply <$> get <*> get
817 get = WhereIsReply <$> get <*> get
669
818
670instance Binary RegisterReply where
819instance Binary RegisterReply where
671− put (RegisterReply label ok) = put label >> put ok
820+ put (RegisterReply label ok owner) = put label >> put ok >> put owner
672− get = RegisterReply <$> get <*> get
821+ get = RegisterReply <$> get <*> get <*> get
673
822
674instance Binary ProcessInfo where
823instance Binary ProcessInfo where
675 get = ProcessInfo <$> get <*> get <*> get <*> get <*> get
824 get = ProcessInfo <$> get <*> get <*> get <*> get <*> get
@@ -679,6 +828,14 @@ instance Binary ProcessInfo where
679 >> put (infoMonitors pInfo)
828 >> put (infoMonitors pInfo)
680 >> put (infoLinks pInfo)
829 >> put (infoLinks pInfo)
681
830
831+instance Binary NodeStats where
832+ get = NodeStats <$> get <*> get <*> get <*> get <*> get
833+ put nStats = put (nodeStatsNode nStats)
834+ >> put (nodeStatsRegisteredNames nStats)
835+ >> put (nodeStatsMonitors nStats)
836+ >> put (nodeStatsLinks nStats)
837+ >> put (nodeStatsProcesses nStats)
838+
682instance Binary ProcessInfoNone where
839instance Binary ProcessInfoNone where
683 get = ProcessInfoNone <$> get
840 get = ProcessInfoNone <$> get
684 put (ProcessInfoNone r) = put r
841 put (ProcessInfoNone r) = put r
@@ -687,22 +844,22 @@ instance Binary ProcessInfoNone where
687-- Accessors --
844-- Accessors --
688--------------------------------------------------------------------------------
845--------------------------------------------------------------------------------
689
846
690−localProcesses :: Accessor LocalNodeState (Map LocalProcessId LocalProcess)
847+localProcesses :: Accessor ValidLocalNodeState (Map LocalProcessId LocalProcess)
691localProcesses = accessor _localProcesses (\procs st -> st { _localProcesses = procs })
848localProcesses = accessor _localProcesses (\procs st -> st { _localProcesses = procs })
692
849
693−localPidCounter :: Accessor LocalNodeState Int32
850+localPidCounter :: Accessor ValidLocalNodeState Int32
694localPidCounter = accessor _localPidCounter (\ctr st -> st { _localPidCounter = ctr })
851localPidCounter = accessor _localPidCounter (\ctr st -> st { _localPidCounter = ctr })
695
852
696−localPidUnique :: Accessor LocalNodeState Int32
853+localPidUnique :: Accessor ValidLocalNodeState Int32
697localPidUnique = accessor _localPidUnique (\unq st -> st { _localPidUnique = unq })
854localPidUnique = accessor _localPidUnique (\unq st -> st { _localPidUnique = unq })
698
855
699−localConnections :: Accessor LocalNodeState (Map (Identifier, Identifier) (NT.Connection, ImplicitReconnect))
856+localConnections :: Accessor ValidLocalNodeState (Map (Identifier, Identifier) (NT.Connection, ImplicitReconnect))
700localConnections = accessor _localConnections (\conns st -> st { _localConnections = conns })
857localConnections = accessor _localConnections (\conns st -> st { _localConnections = conns })
701
858
702−localProcessWithId :: LocalProcessId -> Accessor LocalNodeState (Maybe LocalProcess)
859+localProcessWithId :: LocalProcessId -> Accessor ValidLocalNodeState (Maybe LocalProcess)
703localProcessWithId lpid = localProcesses >>> DAC.mapMaybe lpid
860localProcessWithId lpid = localProcesses >>> DAC.mapMaybe lpid
704
861
705−localConnectionBetween :: Identifier -> Identifier -> Accessor LocalNodeState (Maybe (NT.Connection, ImplicitReconnect))
862+localConnectionBetween :: Identifier -> Identifier -> Accessor ValidLocalNodeState (Maybe (NT.Connection, ImplicitReconnect))
706localConnectionBetween from' to' = localConnections >>> DAC.mapMaybe (from', to')
863localConnectionBetween from' to' = localConnections >>> DAC.mapMaybe (from', to')
707
864
708monitorCounter :: Accessor LocalProcessState Int32
865monitorCounter :: Accessor LocalProcessState Int32
@@ -728,4 +885,3 @@ typedChannelWithId cid = typedChannels >>> DAC.mapMaybe cid
728{-# INLINE forever' #-}
885{-# INLINE forever' #-}
729forever' :: Monad m => m a -> m b
886forever' :: Monad m => m a -> m b
730forever' a = let a' = a >> a' in a'
887forever' a = let a' = a >> a' in a'
731−
src/Control/Distributed/Process/Internal/WeakTQueue.hs+9−3
@@ -1,10 +1,12 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveDataTypeable #-}
3+{-# LANGUAGE MagicHash, UnboxedTuples #-}
1-- | Clone of Control.Concurrent.STM.TQueue with support for mkWeakTQueue
4-- | Clone of Control.Concurrent.STM.TQueue with support for mkWeakTQueue
2--
5--
3-- Not all functionality from the original module is available: unGetTQueue,
6-- Not all functionality from the original module is available: unGetTQueue,
4-- peekTQueue and tryPeekTQueue are missing. In order to implement these we'd
7-- peekTQueue and tryPeekTQueue are missing. In order to implement these we'd
5-- need to be able to touch# the write end of the queue inside unGetTQueue, but
8-- need to be able to touch# the write end of the queue inside unGetTQueue, but
6-- that means we need a version of touch# that works within the STM monad.
9-- that means we need a version of touch# that works within the STM monad.
7−{-# LANGUAGE MagicHash, UnboxedTuples #-}
8module Control.Distributed.Process.Internal.WeakTQueue (
10module Control.Distributed.Process.Internal.WeakTQueue (
9 -- * Original functionality
11 -- * Original functionality
10 TQueue,
12 TQueue,
@@ -21,8 +23,8 @@ module Control.Distributed.Process.Internal.WeakTQueue (
21import Prelude hiding (read)
23import Prelude hiding (read)
22import GHC.Conc
24import GHC.Conc
23import Data.Typeable (Typeable)
25import Data.Typeable (Typeable)
24−import GHC.IO (IO(IO))
26+import GHC.IO (IO(IO), unIO)
25−import GHC.Prim (mkWeak#)
27+import GHC.Exts (mkWeak#)
26import GHC.Weak (Weak(Weak))
28import GHC.Weak (Weak(Weak))
27
29
28--------------------------------------------------------------------------------
30--------------------------------------------------------------------------------
@@ -98,4 +100,8 @@ isEmptyTQueue (TQueue read write) = do
98
100
99mkWeakTQueue :: TQueue a -> IO () -> IO (Weak (TQueue a))
101mkWeakTQueue :: TQueue a -> IO () -> IO (Weak (TQueue a))
100mkWeakTQueue q@(TQueue _read (TVar write#)) f = IO $ \s ->
102mkWeakTQueue 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
101 case mkWeak# write# q f s of (# s', w #) -> (# s', Weak w #)
106 case mkWeak# write# q f s of (# s', w #) -> (# s', Weak w #)
107+#endif
src/Control/Distributed/Process/Management.hs+456−22
@@ -3,8 +3,8 @@
3{-# LANGUAGE StandaloneDeriving #-}
3{-# LANGUAGE StandaloneDeriving #-}
4{-# LANGUAGE ExistentialQuantification #-}
4{-# LANGUAGE ExistentialQuantification #-}
5{-# LANGUAGE ScopedTypeVariables #-}
5{-# LANGUAGE ScopedTypeVariables #-}
6−{-# LANGUAGE PatternGuards #-}
7{-# LANGUAGE RecordWildCards #-}
6{-# LANGUAGE RecordWildCards #-}
7+{-# LANGUAGE PatternGuards #-}
8{-# LANGUAGE FlexibleInstances #-}
8{-# LANGUAGE FlexibleInstances #-}
9{-# LANGUAGE UndecidableInstances #-}
9{-# LANGUAGE UndecidableInstances #-}
10
10
@@ -18,30 +18,278 @@
18-- Stability : experimental
18-- Stability : experimental
19-- Portability : non-portable (requires concurrency)
19-- Portability : non-portable (requires concurrency)
20--
20--
21−-- /Management Extensions/ for Cloud Haskell.
21+-- [Management Extensions API]
22--
22--
23−-- This module provides a standard API for monitoring and/or responding to
23+-- This module presents an API for creating /Management Agents/:
24−-- system events in a Cloud Haskell node.
24+-- special processes that are capable of receiving and responding to
25+-- a node's internal system events. These /system events/ are delivered by
26+-- the management event bus: An internal subsystem maintained for each
27+-- running node, to which all agents are automatically subscribed.
25--
28--
26−-- [Architecture Overview]
29+-- /Agents/ are defined in terms of /event sinks/, taking a particular
30+-- @Serializable@ type and evaluating to an action in the 'MxAgent' monad in
31+-- response. Each 'MxSink' evaluates to an 'MxAction' that specifies whether
32+-- the agent should continue processing it's inputs or stop. If the type of a
33+-- message cannot be matched to any of the agent's sinks, it will be discarded.
34+-- A sink can also deliberately skip processing a message, deferring to the
35+-- remaining handlers. This is the /only/ way that more than one event sink
36+-- can handle the same data type, since otherwise the first /type match/ will
37+-- /win/ every time a message arrives. See 'mxSkip' for details.
38+--
39+-- Various events are published to the management event bus automatically,
40+-- the full list of which can be found in the definition of the 'MxEvent' data
41+-- type. Additionally, clients of the /Management API/ can publish arbitrary
42+-- @Serializable@ data to the event bus using 'mxNotify'. All running agents
43+-- receive all events (from the primary event bus to which they're subscribed).
44+--
45+-- Agent processes are automatically registered on the local node, and can
46+-- receive messages via their mailbox just like ordinary processes. Unlike
47+-- ordinary @Process@ code however, it is unnecessary (though possible) for
48+-- agents to use the base @expect@ and @receiveX@ primitives to do this, since
49+-- the management infrastructure will continuously read from both the primary
50+-- event bus /and/ the process' own mailbox. Messages are transparently passed
51+-- to the agent's event sinks from both sources, so an agent need only concern
52+-- itself with how to respond to its inputs.
53+--
54+-- Some agents may wish to prioritise messages from their mailbox over traffic
55+-- on the management event bus, or vice versa. The 'mxReceive' and
56+-- 'mxReceiveChan' API calls do this for the mailbox and event bus,
57+-- respectively. The /prioritisation/ these APIs offer is simply that the chosen
58+-- data stream will be checked first. No blocking will occur if the chosen
59+-- (prioritised) source is devoid of input messages, instead the agent handling
60+-- code will revert to switching between the alternatives in /round-robin/ as
61+-- usual. If messages exist in one or more channels, they will be consumed as
62+-- soon as they're available, priority is effectively a hint about which
63+-- channel to consume from, should messages be available in both.
64+--
65+-- Prioritisation then, is a /hint/ about the preference of data source from
66+-- which the next input should be chosen. No guarantee can be made that the
67+-- chosen source will in fact be selected at runtime.
68+--
69+-- [Management API Semantics]
70+--
71+-- The management API provides /no guarantees whatsoever/, viz:
72+--
73+-- * The ordering of messages delivered to the event bus.
74+--
75+-- * The order in which agents will be executed.
76+--
77+-- * Whether messages will be taken from the mailbox first, or the event bus.
78+--
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.
27--
112--
28−-- /Management Extensions/ can be used by infrastructure or application code
113+-- [Management Data API]
29−-- to provide access to runtime instrumentation data, expose control planes
30−-- and/or provide remote access to external clients.
31--
114--
32−-- The management infrastructure is broken down into three components, which
115+-- Both management agents and clients of the API have access to a variety of
33−-- roughly correspond to the modules it exposes:
116+-- data storage capabilities, to facilitate publishing and consuming useful
117+-- system information. Agents maintain their own internal state privately (via a
118+-- state transformer - see 'mxGetLocal' et al), however it is possible for
119+-- agents to share additional data with each other (and the outside world)
120+-- using whatever mechanism the user wishes, e.g., acidstate, or shared memory
121+-- primitives.
34--
122--
35−-- 1. @Agent@ - registration and management of /Management Agents/
123+-- [Defining Agents]
36−-- 2. @Instrumentation@ - provides an API for publishing /instrumentation data/
37−-- 3. @Remote@ - provides an API for remote management and administration
38--
124--
125+-- New agents are defined with 'mxAgent' and require a unique 'MxAgentId', an
126+-- initial state - 'MxAgent' runs in a state transformer - and a list of the
127+-- agent's event sinks. Each 'MxSink' is defined in terms of a specific
128+-- @Serializable@ type, via the 'mxSink' function, binding the event handler
129+-- expression to inputs of only that type.
39--
130--
131+-- Apart from modifying its own local state, an agent can execute arbitrary
132+-- @Process a@ code via lifting (see 'liftMX') and even publish its own messages
133+-- back to the primary event bus (see 'mxBroadcast').
134+--
135+-- Since messages are delivered to agents from both the management event bus and
136+-- the agent processes mailbox, agents (i.e., event sinks) will generally have
137+-- no idea as to their origin. An agent can, however, choose to prioritise the
138+-- choice of input (source) each time one of its event sinks runs. The /standard/
139+-- way for an event sink to indicate that the agent is ready for its next input
140+-- is to evaluate 'mxReady'. When this happens, the management infrastructure
141+-- will obtain data from the event bus and process' mailbox in a round robbin
142+-- fashion, i.e., one after the other, changing each time.
143+--
144+-- [Example Code]
145+--
146+-- What follows is a grossly over-simplified example of a management agent that
147+-- provides a basic name monitoring facility. Whenever a process name is
148+-- registered or unregistered, clients are informed of the fact.
149+--
150+-- > -- simple notification data type
151+-- >
152+-- > data Registration = Reg { added :: Bool
153+-- > , procId :: ProcessId
154+-- > , name :: String
155+-- > }
156+-- >
157+-- > -- start a /name monitoring agent/
158+-- > nameMonitorAgent = do
159+-- > mxAgent (MxAgentId "name-monitor") Set.empty [
160+-- > (mxSink $ \(pid :: ProcessId) -> do
161+-- > mxUpdateState $ Set.insert pid
162+-- > mxReady)
163+-- > , (mxSink $
164+-- > let act =
165+-- > case ev of
166+-- > (MxRegistered p n) -> notify True n p
167+-- > (MxUnRegistered p n) -> notify False n p
168+-- > _ -> return ()
169+-- > act >> mxReady)
170+-- > ]
171+-- > where
172+-- > notify a n p = do
173+-- > Foldable.mapM_ (liftMX . deliver (Reg a n p)) =<< mxGetLocal
174+-- >
175+--
176+-- The client interface (for sending their pid) can take one of two forms:
177+--
178+-- > monitorNames = getSelfPid >>= nsend "name-monitor"
179+-- > monitorNames2 = getSelfPid >>= mxNotify
180+--
181+-- [Performance, Stablity and Scalability]
182+--
183+-- /Management Agents/ offer numerous advantages over regular processes:
184+-- broadcast communication with them can have a lower latency, they offer
185+-- simplified messgage (i.e., input type) handling and they have access to
186+-- internal system information that would be otherwise unobtainable.
187+--
188+-- Do not be tempted to implement everything (e.g., the kitchen sink) using the
189+-- management API though. There are overheads associated with management agents
190+-- which is why they're presented as tools for consuming low level system
191+-- information, instead of as /application level/ development tools.
192+--
193+-- Agents that rely heavily on a busy mailbox can cause the management event
194+-- bus to backlog un-GC'ed data, leading to increased heap space. Producers that
195+-- do not take care to avoid passing unevaluated thunks to the API can crash
196+-- /all/ the agents in the system. Agents are not monitored or managed in any
197+-- way, and those that crash will not be restarted.
198+--
199+-- The management event bus can receive a great deal of traffic. Every time
200+-- a message is sent and/or received, an event is passed to the agent controller
201+-- and broadcast to all agents (plus the trace controller, if tracing is enabled
202+-- for the node). This is already a significant overhead - though profiling and
203+-- benchmarks have demonstrated that it does not adversely affect performance
204+-- if few agents are installed. Agents will typically use more cycles than plain
205+-- processes, since they perform additional work: selecting input data from both
206+-- the event bus /and/ their own mailboxes, plus searching through the set of
207+-- event sinks (for each agent) to determine the right handler for the event.
208+--
209+-- [Architecture Overview]
210+--
211+-- The architecture of the management event bus is internal and subject to
212+-- change without prior notice. The description that follows is provided for
213+-- informational purposes only.
214+--
215+-- When a node initially starts, two special, internal system processes are
216+-- started to support the management infrastructure. The first, known as the
217+-- /trace controller/, is responsible for consuming 'MxEvent's and forwarding
218+-- them to the configured tracer - see "Control.Distributed.Process.Debug" for
219+-- further details. The second is the /management agent controller/, and is the
220+-- primary worker process underpinning the management infrastructure. All
221+-- published management events are routed to this process, which places them
222+-- onto a system wide /event bus/ and additionally passes them directly to the
223+-- /trace controller/.
224+--
225+-- There are several reasons for segregating the tracing and management control
226+-- planes in this fashion. Tracing can be enabled or disabled by clients, whilst
227+-- the management event bus cannot, since in addition to providing
228+-- runtime instrumentation, its intended use-cases include node monitoring, peer
229+-- discovery (via topology providing backends) and other essential system
230+-- services that require knowledge of otherwise hidden system internals. Tracing
231+-- is also subject to /trace flags/ that limit the specific 'MxEvent's delivered
232+-- to trace clients - an overhead/complexity not shared by management agents.
233+-- Finally, tracing and management agents are implemented using completely
234+-- different signalling techniques - more on this later - which would introduce
235+-- considerable complexity if the shared the same /event loop/.
236+--
237+-- The management control plane is driven by a shared broadcast channel, which
238+-- is written to by the agent controller and subscribed to by all agent
239+-- processes. Agents are spawned as regular processes, whose primary
240+-- implementation (i.e., /server loop/) is responsible for consuming
241+-- messages from both the broadcast channel and their own mailbox. Once
242+-- consumed, messages are applied to the agent's /event sinks/ until one
243+-- matches the input, at which point it is applied and the loop continues.
244+-- The implementation chooses from the event bus and the mailbox in a
245+-- round-robin fashion, until a message is received. This polling activity would
246+-- lead to management agents consuming considerable system resources if left
247+-- unchecked, therefore the implementation will poll for a limitted number of
248+-- retries, after which it will perform a blocking read on the event bus.
40--
249--
41-----------------------------------------------------------------------------
250-----------------------------------------------------------------------------
42−module Control.Distributed.Process.Management where
251+module Control.Distributed.Process.Management
252+ (
253+ MxEvent(..)
254+ -- * Firing Arbitrary /Mx Events/
255+ , mxNotify
256+ -- * Constructing Mx Agents
257+ , MxAction()
258+ , MxAgentId(..)
259+ , MxAgent()
260+ , mxAgent
261+ , mxAgentWithFinalize
262+ , MxSink()
263+ , mxSink
264+ , mxGetId
265+ , mxDeactivate
266+ , mxReady
267+ , mxSkip
268+ , mxReceive
269+ , mxReceiveChan
270+ , mxBroadcast
271+ , mxSetLocal
272+ , mxGetLocal
273+ , mxUpdateLocal
274+ , liftMX
275+ ) where
43
276
44−import Control.Applicative ((<$>))
277+import Control.Applicative
278+import Control.Concurrent.STM (atomically)
279+import Control.Concurrent.STM.TChan
280+ ( readTChan
281+ , writeTChan
282+ , TChan
283+ )
284+import Control.Distributed.Process.Internal.Primitives
285+ ( receiveWait
286+ , matchAny
287+ , matchSTM
288+ , unwrapMessage
289+ , register
290+ , whereis
291+ , die
292+ )
45import Control.Distributed.Process.Internal.Types
293import Control.Distributed.Process.Internal.Types
46 ( Process
294 ( Process
47 , ProcessId
295 , ProcessId
@@ -51,10 +299,27 @@ import Control.Distributed.Process.Internal.Types
51 , MxEventBus(..)
299 , MxEventBus(..)
52 , unsafeCreateUnencodedMessage
300 , unsafeCreateUnencodedMessage
53 )
301 )
54−import Control.Distributed.Process.Management.Bus (publishEvent)
302+import Control.Distributed.Process.Management.Internal.Bus (publishEvent)
303+import Control.Distributed.Process.Management.Internal.Types
304+ ( MxAgentId(..)
305+ , MxAgent(..)
306+ , MxAction(..)
307+ , ChannelSelector(..)
308+ , MxAgentState(..)
309+ , MxSink
310+ , MxEvent(..)
311+ )
55import Control.Distributed.Process.Serializable (Serializable)
312import Control.Distributed.Process.Serializable (Serializable)
56import Control.Monad.IO.Class (liftIO)
313import Control.Monad.IO.Class (liftIO)
57import Control.Monad.Reader (ask)
314import Control.Monad.Reader (ask)
315+import Control.Monad.Catch (onException)
316+import qualified Control.Monad.State as ST
317+ ( get
318+ , modify
319+ , lift
320+ , runStateT
321+ )
322+import Prelude
58
323
59-- | Publishes an arbitrary @Serializable@ message to the management event bus.
324-- | Publishes an arbitrary @Serializable@ message to the management event bus.
60-- Note that /no attempt is made to force the argument/, therefore it is very
325-- Note that /no attempt is made to force the argument/, therefore it is very
@@ -66,10 +331,179 @@ mxNotify msg = do
66 bus <- localEventBus . processNode <$> ask
331 bus <- localEventBus . processNode <$> ask
67 liftIO $ publishEvent bus $ unsafeCreateUnencodedMessage msg
332 liftIO $ publishEvent bus $ unsafeCreateUnencodedMessage msg
68
333
334+--------------------------------------------------------------------------------
335+-- API for writing user defined management extensions (i.e., agents) --
336+--------------------------------------------------------------------------------
337+
338+-- | Return the 'MxAgentId' for the currently executing agent.
339+--
340+mxGetId :: MxAgent s MxAgentId
341+mxGetId = ST.get >>= return . mxAgentId
342+
343+-- | The 'MxAgent' version of 'mxNotify'.
344+--
345+mxBroadcast :: (Serializable m) => m -> MxAgent s ()
346+mxBroadcast msg = do
347+ state <- ST.get
348+ liftMX $ liftIO $ atomically $ do
349+ writeTChan (mxBus state) (unsafeCreateUnencodedMessage msg)
350+
351+-- | Gracefully terminate an agent.
352+--
353+mxDeactivate :: forall s. String -> MxAgent s MxAction
354+mxDeactivate = return . MxAgentDeactivate
355+
356+-- | Continue executing (i.e., receiving and processing messages).
357+--
358+mxReady :: forall s. MxAgent s MxAction
359+mxReady = return MxAgentReady
360+
361+-- | Causes the currently executing /event sink/ to be skipped.
362+-- The remaining declared event sinks will be evaluated to find
363+-- a matching handler. Can be used to allow multiple event sinks
364+-- to process data of the same type.
365+--
366+mxSkip :: forall s. MxAgent s MxAction
367+mxSkip = return MxAgentSkip
368+
369+-- | Continue exeucting, prioritising inputs from the process' own
370+-- /mailbox/ ahead of data from the management event bus.
371+--
372+mxReceive :: forall s. MxAgent s MxAction
373+mxReceive = return $ MxAgentPrioritise Mailbox
374+
375+-- | Continue exeucting, prioritising inputs from the management event bus
376+-- over the process' own /mailbox/.
377+--
378+mxReceiveChan :: forall s. MxAgent s MxAction
379+mxReceiveChan = return $ MxAgentPrioritise InputChan
380+
381+-- | Lift a @Process@ action.
382+--
383+liftMX :: Process a -> MxAgent s a
384+liftMX p = MxAgent $ ST.lift p
385+
386+-- | Set the agent's local state.
387+--
388+mxSetLocal :: s -> MxAgent s ()
389+mxSetLocal s = ST.modify $ \st -> st { mxLocalState = s }
390+
391+-- | Update the agent's local state.
392+--
393+mxUpdateLocal :: (s -> s) -> MxAgent s ()
394+mxUpdateLocal f = ST.modify $ \st -> st { mxLocalState = (f $ mxLocalState st) }
395+
396+-- | Fetch the agent's local state.
397+--
398+mxGetLocal :: MxAgent s s
399+mxGetLocal = ST.get >>= return . mxLocalState
400+
401+-- | Create an 'MxSink' from an expression taking a @Serializable@ type @m@,
402+-- that yields an 'MxAction' in the 'MxAgent' monad.
403+--
404+mxSink :: forall s m . (Serializable m)
405+ => (m -> MxAgent s MxAction)
406+ -> MxSink s
407+mxSink act msg = do
408+ msg' <- liftMX $ (unwrapMessage msg :: Process (Maybe m))
409+ case msg' of
410+ Nothing -> return Nothing
411+ Just m -> do
412+ r <- act m
413+ case r of
414+ MxAgentSkip -> return Nothing
415+ _ -> return $ Just r
416+
417+-- private ADT: a linked list of event sinks
418+data MxPipeline s =
419+ MxPipeline
420+ {
421+ current :: !(MxSink s)
422+ , next :: !(MxPipeline s)
423+ } | MxStop
424+
425+-- | Activates a new agent.
426+--
69−mxAgent :: (Message -> Process ()) -> Process ProcessId
427+mxAgent :: MxAgentId -> s -> [MxSink s] -> Process ProcessId
70−mxAgent handler = do
428+mxAgent mxId st hs = mxAgentWithFinalize mxId st hs $ return ()
429+
430+-- | Activates a new agent. This variant takes a /finalizer/ expression,
431+-- that is run once the agent shuts down (even in case of failure/exceptions).
432+-- The /finalizer/ expression runs in the mx monad - @MxAgent s ()@ - such
433+-- that the agent's internal state remains accessible to the shutdown/cleanup
434+-- code.
435+--
436+mxAgentWithFinalize :: MxAgentId
437+ -> s
438+ -> [MxSink s]
439+ -> MxAgent s ()
440+ -> Process ProcessId
441+mxAgentWithFinalize mxId initState handlers dtor = do
442+ let name = agentId mxId
443+ existing <- whereis name
444+ case existing of
445+ Just _ -> die "DuplicateAgentId" -- TODO: better error handling policy
446+ Nothing -> do
71− node <- processNode <$> ask
447+ node <- processNode <$> ask
72− pid <- liftIO $ mxNew (localEventBus node) handler
448+ pid <- liftIO $ mxNew (localEventBus node) $ start
73− -- registerAgent pid
449+ register name pid
74− return pid
450+ return pid
451+ where
452+ start (sendTChan, recvTChan) = do
453+ let nState = MxAgentState mxId sendTChan initState
454+ runAgent dtor handlers InputChan recvTChan nState
455+
456+ runAgent :: MxAgent s ()
457+ -> [MxSink s]
458+ -> ChannelSelector
459+ -> TChan Message
460+ -> MxAgentState s
461+ -> Process ()
462+ runAgent eh hs cs c s =
463+ runAgentWithFinalizer eh hs cs c s
464+ `onException` runAgentFinalizer eh s
465+
466+ runAgentWithFinalizer :: MxAgent s ()
467+ -> [MxSink s]
468+ -> ChannelSelector
469+ -> TChan Message
470+ -> MxAgentState s
471+ -> Process ()
472+ runAgentWithFinalizer eh' hs' cs' c' s' = do
473+ msg <- getNextInput cs' c'
474+ (action, state) <- runPipeline msg s' $ pipeline hs'
475+ case action of
476+ MxAgentReady -> runAgent eh' hs' InputChan c' state
477+ MxAgentPrioritise priority -> runAgent eh' hs' priority c' state
478+ MxAgentDeactivate _ -> runAgentFinalizer eh' state
479+ MxAgentSkip -> error "IllegalState"
480+-- MxAgentBecome h' -> runAgent h' c state
481+
482+ getNextInput sel chan =
483+ let matches =
484+ case sel of
485+ Mailbox -> [ matchAny return
486+ , matchSTM (readTChan chan) return]
487+ InputChan -> [ matchSTM (readTChan chan) return
488+ , matchAny return]
489+ in receiveWait matches
490+
491+ runAgentFinalizer :: MxAgent s () -> MxAgentState s -> Process ()
492+ runAgentFinalizer f s = ST.runStateT (unAgent f) s >>= return . fst
493+
494+ pipeline :: forall s . [MxSink s] -> MxPipeline s
495+ pipeline [] = MxStop
496+ pipeline (sink:sinks) = MxPipeline sink (pipeline sinks)
75
497
498+ runPipeline :: forall s .
499+ Message
500+ -> MxAgentState s
501+ -> MxPipeline s
502+ -> Process (MxAction, MxAgentState s)
503+ runPipeline _ state MxStop = return (MxAgentReady, state)
504+ runPipeline msg state MxPipeline{..} = do
505+ let act = current msg
506+ (pass, state') <- ST.runStateT (unAgent act) state
507+ case pass of
508+ Nothing -> runPipeline msg state next
509+ Just result -> return (result, state')
src/Control/Distributed/Process/Management/Agent.hs → src/Control/Distributed/Process/Management/Internal/Agent.hsrenamed+49−27
@@ -1,8 +1,10 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
1{-# LANGUAGE RecordWildCards #-}
2{-# LANGUAGE RecordWildCards #-}
2
3
3−module Control.Distributed.Process.Management.Agent where
4+module Control.Distributed.Process.Management.Internal.Agent where
4
5
5−import Control.Applicative ((<$>))
6+import Control.Applicative
7+import Control.Concurrent (forkIO)
6import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
8import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
7import Control.Concurrent.STM (atomically)
9import Control.Concurrent.STM (atomically)
8import Control.Concurrent.STM.TChan
10import Control.Concurrent.STM.TChan
@@ -23,43 +25,41 @@ import Control.Distributed.Process.Internal.CQueue
23 ( enqueueSTM
25 ( enqueueSTM
24 , CQueue
26 , CQueue
25 )
27 )
26−import Control.Distributed.Process.Internal.Trace.Tracer
28+import Control.Distributed.Process.Management.Internal.Types
29+ ( Fork
30+ )
31+import Control.Distributed.Process.Management.Internal.Trace.Tracer
27 ( traceController
32 ( traceController
28 )
33 )
29import Control.Distributed.Process.Internal.Types
34import Control.Distributed.Process.Internal.Types
30 ( Process
35 ( Process
31− , SendPort
32 , Message
36 , Message
33− , MxEventBus(..)
34 , Tracer(..)
37 , Tracer(..)
35− , LocalNode(..)
36 , LocalProcess(..)
38 , LocalProcess(..)
37 , ProcessId
39 , ProcessId
38 , forever'
40 , forever'
39 )
41 )
40import Control.Exception (AsyncException(ThreadKilled), SomeException)
42import Control.Exception (AsyncException(ThreadKilled), SomeException)
41−import qualified Control.Exception as Ex (catch, finally)
42import Control.Monad (void)
43import Control.Monad (void)
43import Control.Monad.IO.Class (liftIO)
44import Control.Monad.IO.Class (liftIO)
44import Control.Monad.Reader (ask)
45import Control.Monad.Reader (ask)
45−import GHC.Weak (Weak(Weak), deRefWeak)
46+import GHC.Weak (Weak, deRefWeak)
46−import System.Environment (getEnv)
47+import Prelude
47
48
48−-- | Gross though it is, this synonym represents a function
49+--------------------------------------------------------------------------------
49−-- used to forking new processes, which has to be passed as a HOF
50+-- Agent Controller Implementation --
50−-- when calling mxAgentController, since there's no other way to
51+--------------------------------------------------------------------------------
51−-- avoid a circular dependency with Node.hs
52−type Fork = (Process () -> IO ProcessId)
53
52
54-- | A triple containing a configured tracer, weak pointer to the
53-- | A triple containing a configured tracer, weak pointer to the
55-- agent controller's mailbox (CQueue) and an expression used to
54-- agent controller's mailbox (CQueue) and an expression used to
56-- instantiate new agents on the current node.
55-- instantiate new agents on the current node.
57type AgentConfig =
56type AgentConfig =
58− (Tracer, Weak (CQueue Message), ((Message -> Process ()) -> IO ProcessId))
57+ (Tracer, Weak (CQueue Message),
58+ (((TChan Message, TChan Message) -> Process ()) -> IO ProcessId))
59
59
60-- | Starts a management agent for the current node. The agent process
60-- | Starts a management agent for the current node. The agent process
61-- must not crash or be killed, so we generally avoid publishing its
61-- must not crash or be killed, so we generally avoid publishing its
62−-- 'ProcessId' where possible.
62+-- @ProcessId@ where possible.
63--
63--
64-- Our process is also responsible for forwarding messages to the trace
64-- Our process is also responsible for forwarding messages to the trace
65-- controller, since having two /special processes/ handled via the
65-- controller, since having two /special processes/ handled via the
@@ -75,14 +75,14 @@ mxAgentController :: Fork
75 -> MVar AgentConfig
75 -> MVar AgentConfig
76 -> Process ()
76 -> Process ()
77mxAgentController forkProcess mv = do
77mxAgentController forkProcess mv = do
78− node <- processNode <$> ask
79 trc <- liftIO $ startTracing forkProcess
78 trc <- liftIO $ startTracing forkProcess
80 sigbus <- liftIO $ newBroadcastTChanIO
79 sigbus <- liftIO $ newBroadcastTChanIO
80+ liftIO $ startDeadLetterQueue sigbus
81 weakQueue <- processWeakQ <$> ask
81 weakQueue <- processWeakQ <$> ask
82− liftIO $ putMVar mv (trc, weakQueue, mxAgent forkProcess sigbus)
82+ liftIO $ putMVar mv (trc, weakQueue, mxStartAgent forkProcess sigbus)
83− go sigbus trc forkProcess
83+ go sigbus trc
84 where
84 where
85− go bus tracer fork = forever' $ do
85+ go bus tracer = forever' $ do
86 void $ receiveWait [
86 void $ receiveWait [
87 -- This is exactly what it appears to be: a "catch all" handler.
87 -- This is exactly what it appears to be: a "catch all" handler.
88 -- Since mxNotify can potentially pass an unevaluated thunk to
88 -- Since mxNotify can potentially pass an unevaluated thunk to
@@ -99,7 +99,7 @@ mxAgentController forkProcess mv = do
99 -- (e.g., passing unevaluated thunks that will crash when
99 -- (e.g., passing unevaluated thunks that will crash when
100 -- they're eventually forced) are thus silently ignored.
100 -- they're eventually forced) are thus silently ignored.
101 --
101 --
102− matchAny (\msg -> liftIO $ broadcast bus tracer msg)
102+ matchAny (liftIO . broadcast bus tracer)
103 ] `catches` [Handler (\ThreadKilled -> die "Killed"),
103 ] `catches` [Handler (\ThreadKilled -> die "Killed"),
104 Handler (\(_ :: SomeException) -> return ())]
104 Handler (\(_ :: SomeException) -> return ())]
105
105
@@ -116,16 +116,22 @@ mxAgentController forkProcess mv = do
116 -> Message -> IO ()
116 -> Message -> IO ()
117 atomicBroadcast ch Nothing msg = liftIO $ atomically $ writeTChan ch msg
117 atomicBroadcast ch Nothing msg = liftIO $ atomically $ writeTChan ch msg
118 atomicBroadcast ch (Just q) msg = do
118 atomicBroadcast ch (Just q) msg = do
119+ -- liftIO $ putStrLn $ "broadcasting " ++ (show msg)
119 liftIO $ atomically $ enqueueSTM q msg >> writeTChan ch msg
120 liftIO $ atomically $ enqueueSTM q msg >> writeTChan ch msg
120
121
121-- | Forks a new process in which an mxAgent is run.
122-- | Forks a new process in which an mxAgent is run.
123+--
124+mxStartAgent :: Fork
125+ -> TChan Message
122−mxAgent :: Fork -> TChan Message -> (Message -> Process ()) -> IO ProcessId
126+ -> ((TChan Message, TChan Message) -> Process ())
123−mxAgent fork chan handler = (atomically (dupTChan chan)) >>= fork . run handler
127+ -> IO ProcessId
124− where
128+mxStartAgent fork chan handler = do
125− run :: (Message -> Process ()) -> TChan Message -> Process ()
129+ chan' <- atomically (dupTChan chan)
126− run handler' chan' = do
130+ let proc = handler (chan, chan')
127− (liftIO $ atomically $ readTChan chan') >>= handler' >> run handler' chan'
131+ fork proc
128
132
133+-- | Start the tracer controller.
134+--
129startTracing :: Fork -> IO Tracer
135startTracing :: Fork -> IO Tracer
130startTracing forkProcess = do
136startTracing forkProcess = do
131 mv <- newEmptyMVar
137 mv <- newEmptyMVar
@@ -133,3 +139,19 @@ startTracing forkProcess = do
133 wQ <- liftIO $ takeMVar mv
139 wQ <- liftIO $ takeMVar mv
134 return $ Tracer pid wQ
140 return $ Tracer pid wQ
135
141
142+-- | Start a dead letter (agent) queue.
143+--
144+-- If no agents are registered on the system, the management
145+-- event bus will fill up and its data won't be GC'ed until someone
146+-- comes along and reads from the broadcast channel (via dupTChan
147+-- of course). This is effectively a leak, so to mitigate it, we
148+-- start a /dead letter queue/ that drains the event bus continuously,
149+-- thus ensuring if there are no other consumers that we won't use
150+-- up heap space unnecessarily.
151+--
152+startDeadLetterQueue :: TChan Message
153+ -> IO ()
154+startDeadLetterQueue sigbus = do
155+ chan' <- atomically (dupTChan sigbus)
156+ void $ forkIO $ forever' $ do
157+ void $ atomically $ readTChan chan'
src/Control/Distributed/Process/Management/Bus.hs → src/Control/Distributed/Process/Management/Internal/Bus.hsrenamed+1−1
@@ -1,5 +1,5 @@
1-- | Interface to the management event bus.
1-- | Interface to the management event bus.
2−module Control.Distributed.Process.Management.Bus
2+module Control.Distributed.Process.Management.Internal.Bus
3 ( publishEvent
3 ( publishEvent
4 ) where
4 ) where
5
5
src/Control/Distributed/Process/Internal/Trace/Primitives.hs → src/Control/Distributed/Process/Management/Internal/Trace/Primitives.hsrenamed+18−14
@@ -1,3 +1,5 @@
1+{-# LANGUAGE FlexibleInstances #-}
2+{-# LANGUAGE TypeSynonymInstances #-}
1-- | Keeps the tracing API calls separate from the Tracer implementation,
3-- | Keeps the tracing API calls separate from the Tracer implementation,
2-- which allows us to avoid a nasty import cycle between tracing and
4-- which allows us to avoid a nasty import cycle between tracing and
3-- the messaging primitives that rely on it, and also between the node
5-- the messaging primitives that rely on it, and also between the node
@@ -5,11 +7,9 @@
5-- our RemoteTable) and the Debug module, which requires @forkProcess@.
7-- our RemoteTable) and the Debug module, which requires @forkProcess@.
6-- This module is also used by the management agent, which relies on the
8-- This module is also used by the management agent, which relies on the
7-- tracing infrastructure's messaging fabric.
9-- tracing infrastructure's messaging fabric.
8−module Control.Distributed.Process.Internal.Trace.Primitives
10+module Control.Distributed.Process.Management.Internal.Trace.Primitives
9− ( Tracer
10− , TraceEvent(..)
11− -- * Sending Trace Data
11+ ( -- * Sending Trace Data
12− , traceLog
12+ traceLog
13 , traceLogFmt
13 , traceLogFmt
14 , traceMessage
14 , traceMessage
15 -- * Configuring A Tracer
15 -- * Configuring A Tracer
@@ -28,21 +28,21 @@ module Control.Distributed.Process.Internal.Trace.Primitives
28 , withRegisteredTracer
28 , withRegisteredTracer
29 ) where
29 ) where
30
30
31−import Control.Applicative ((<$>))
31+import Control.Applicative
32import Control.Distributed.Process.Internal.Primitives
32import Control.Distributed.Process.Internal.Primitives
33 ( whereis
33 ( whereis
34 , newChan
34 , newChan
35 , receiveChan
35 , receiveChan
36+ , die
36 )
37 )
37−import Control.Distributed.Process.Internal.Trace.Types
38+import Control.Distributed.Process.Management.Internal.Trace.Types
38 ( TraceArg(..)
39 ( TraceArg(..)
39− , TraceEvent(..)
40 , TraceFlags(..)
40 , TraceFlags(..)
41 , TraceOk(..)
41 , TraceOk(..)
42 , TraceSubject(..)
42 , TraceSubject(..)
43 , defaultTraceFlags
43 , defaultTraceFlags
44 )
44 )
45−import qualified Control.Distributed.Process.Internal.Trace.Types as Tracer
45+import qualified Control.Distributed.Process.Management.Internal.Trace.Types as Tracer
46 ( traceLog
46 ( traceLog
47 , traceLogFmt
47 , traceLogFmt
48 , traceMessage
48 , traceMessage
@@ -56,8 +56,7 @@ import qualified Control.Distributed.Process.Internal.Trace.Types as Tracer
56 , getCurrentTraceClient
56 , getCurrentTraceClient
57 )
57 )
58import Control.Distributed.Process.Internal.Types
58import Control.Distributed.Process.Internal.Types
59− ( Tracer(..)
60− , Process
59+ ( Process
61 , ProcessId
60 , ProcessId
62 , LocalProcess(..)
61 , LocalProcess(..)
63 , LocalNode(localEventBus)
62 , LocalNode(localEventBus)
@@ -68,8 +67,8 @@ import Control.Distributed.Process.Serializable
68import Control.Monad.IO.Class (liftIO)
67import Control.Monad.IO.Class (liftIO)
69import Control.Monad.Reader (ask)
68import Control.Monad.Reader (ask)
70
69
71−import Data.Maybe (isJust)
72import qualified Data.Set as Set (fromList)
70import qualified Data.Set as Set (fromList)
71+import Prelude
73
72
74--------------------------------------------------------------------------------
73--------------------------------------------------------------------------------
75-- Main API --
74-- Main API --
@@ -170,6 +169,11 @@ withRegisteredTracer act = do
170 withLocalTracer $ \t -> liftIO $ Tracer.getCurrentTraceClient t sp
169 withLocalTracer $ \t -> liftIO $ Tracer.getCurrentTraceClient t sp
171 currentTracer <- receiveChan rp
170 currentTracer <- receiveChan rp
172 case currentTracer of
171 case currentTracer of
173− 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)"
174 (Just p) -> act p
179 (Just p) -> act p
175−
src/Control/Distributed/Process/Internal/Trace/Remote.hs → src/Control/Distributed/Process/Management/Internal/Trace/Remote.hsrenamed+4−5
@@ -1,5 +1,5 @@
1−module Control.Distributed.Process.Internal.Trace.Remote
1+module Control.Distributed.Process.Management.Internal.Trace.Remote
2− ( -- * Configuring A Tracer
2+ ( -- * Configuring A Remote Tracer
3 setTraceFlagsRemote
3 setTraceFlagsRemote
4 , startTraceRelay
4 , startTraceRelay
5 -- * Remote Table
5 -- * Remote Table
@@ -14,11 +14,11 @@ import Control.Distributed.Process.Internal.Primitives
14 , relay
14 , relay
15 , nsendRemote
15 , nsendRemote
16 )
16 )
17−import Control.Distributed.Process.Internal.Trace.Types
17+import Control.Distributed.Process.Management.Internal.Trace.Types
18 ( TraceFlags(..)
18 ( TraceFlags(..)
19 , TraceOk(..)
19 , TraceOk(..)
20 )
20 )
21−import Control.Distributed.Process.Internal.Trace.Primitives
21+import Control.Distributed.Process.Management.Internal.Trace.Primitives
22 ( withRegisteredTracer
22 ( withRegisteredTracer
23 , enableTrace
23 , enableTrace
24 )
24 )
@@ -50,7 +50,6 @@ enableTraceRemote pid =
50startTraceRelay :: NodeId -> Process ProcessId
50startTraceRelay :: NodeId -> Process ProcessId
51startTraceRelay nodeId = do
51startTraceRelay nodeId = do
52 withRegisteredTracer $ \pid ->
52 withRegisteredTracer $ \pid ->
53− -- TODO: move cpEnableTraceRemote here?
54 spawn nodeId $ cpEnableTraceRemote pid
53 spawn nodeId $ cpEnableTraceRemote pid
55
54
56-- | Set the given flags for a remote node (asynchronous).
55-- | Set the given flags for a remote node (asynchronous).
src/Control/Distributed/Process/Internal/Trace/Tracer.hs → src/Control/Distributed/Process/Management/Internal/Trace/Tracer.hsrenamed+64−50
@@ -1,5 +1,7 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
1-- | Tracing/Debugging support - Trace Implementation
3-- | Tracing/Debugging support - Trace Implementation
2−module Control.Distributed.Process.Internal.Trace.Tracer
4+module Control.Distributed.Process.Management.Internal.Trace.Tracer
3 ( -- * API for the Management Agent
5 ( -- * API for the Management Agent
4 traceController
6 traceController
5 -- * Built in tracers
7 -- * Built in tracers
@@ -9,7 +11,7 @@ module Control.Distributed.Process.Internal.Trace.Tracer
9 , eventLogTracer
11 , eventLogTracer
10 ) where
12 ) where
11
13
12−import Control.Applicative ((<$>))
14+import Control.Applicative
13import Control.Concurrent.Chan (writeChan)
15import Control.Concurrent.Chan (writeChan)
14import Control.Concurrent.MVar
16import Control.Concurrent.MVar
15 ( MVar
17 ( MVar
@@ -19,9 +21,7 @@ import Control.Distributed.Process.Internal.CQueue
19 ( CQueue
21 ( CQueue
20 )
22 )
21import Control.Distributed.Process.Internal.Primitives
23import Control.Distributed.Process.Internal.Primitives
22− ( catch
23− , finally
24− , die
24+ ( die
25 , receiveWait
25 , receiveWait
26 , forward
26 , forward
27 , sendChan
27 , sendChan
@@ -31,16 +31,18 @@ import Control.Distributed.Process.Internal.Primitives
31 , handleMessage
31 , handleMessage
32 , matchUnknown
32 , matchUnknown
33 )
33 )
34−import Control.Distributed.Process.Internal.Trace.Types
34+import Control.Distributed.Process.Management.Internal.Types
35− ( TraceEvent(..)
35+ ( MxEvent(..)
36− , SetTrace(..)
37 , Addressable(..)
36 , Addressable(..)
37+ )
38+import Control.Distributed.Process.Management.Internal.Trace.Types
39+ ( SetTrace(..)
38 , TraceSubject(..)
40 , TraceSubject(..)
39 , TraceFlags(..)
41 , TraceFlags(..)
40 , TraceOk(..)
42 , TraceOk(..)
41 , defaultTraceFlags
43 , defaultTraceFlags
42 )
44 )
43−import Control.Distributed.Process.Internal.Trace.Primitives
45+import Control.Distributed.Process.Management.Internal.Trace.Primitives
44 ( traceOn )
46 ( traceOn )
45import Control.Distributed.Process.Internal.Types
47import Control.Distributed.Process.Internal.Types
46 ( LocalNode(..)
48 ( LocalNode(..)
@@ -59,6 +61,10 @@ import Control.Distributed.Process.Internal.Types
59
61
60import Control.Monad.IO.Class (liftIO)
62import Control.Monad.IO.Class (liftIO)
61import Control.Monad.Reader (ask)
63import Control.Monad.Reader (ask)
64+import Control.Monad.Catch
65+ ( catch
66+ , finally
67+ )
62
68
63import Data.Set (Set)
69import Data.Set (Set)
64import qualified Data.Set as Set
70import qualified Data.Set as Set
@@ -69,6 +75,7 @@ import Data.Maybe (fromMaybe)
69import Data.Time.Clock (getCurrentTime)
75import Data.Time.Clock (getCurrentTime)
70import Data.Time.Format (formatTime)
76import Data.Time.Format (formatTime)
71import Debug.Trace (traceEventIO)
77import Debug.Trace (traceEventIO)
78+import Prelude
72
79
73#if ! MIN_VERSION_base(4,6,0)
80#if ! MIN_VERSION_base(4,6,0)
74import Prelude hiding (catch)
81import Prelude hiding (catch)
@@ -84,7 +91,11 @@ import System.IO
84 , hPutStrLn
91 , hPutStrLn
85 , hSetBuffering
92 , hSetBuffering
86 )
93 )
94+#if MIN_VERSION_time(1,5,0)
95+import Data.Time.Format (defaultTimeLocale)
96+#else
87import System.Locale (defaultTimeLocale)
97import System.Locale (defaultTimeLocale)
98+#endif
88import System.Mem.Weak
99import System.Mem.Weak
89 ( Weak
100 ( Weak
90 )
101 )
@@ -132,24 +143,24 @@ nullTracer =
132systemLoggerTracer :: Process ()
143systemLoggerTracer :: Process ()
133systemLoggerTracer = do
144systemLoggerTracer = do
134 node <- processNode <$> ask
145 node <- processNode <$> ask
135− let tr = sendTraceMsg node
146+ let tr = sendTraceLog node
136 forever' $ receiveWait [ matchAny (\m -> handleMessage m tr) ]
147 forever' $ receiveWait [ matchAny (\m -> handleMessage m tr) ]
137 where
148 where
138− sendTraceMsg :: LocalNode -> TraceEvent -> Process ()
149+ sendTraceLog :: LocalNode -> MxEvent -> Process ()
139− sendTraceMsg node ev = do
150+ sendTraceLog node ev = do
140 now <- liftIO $ getCurrentTime
151 now <- liftIO $ getCurrentTime
141 msg <- return $ (formatTime defaultTimeLocale "%c" now, buildTxt ev)
152 msg <- return $ (formatTime defaultTimeLocale "%c" now, buildTxt ev)
142 emptyPid <- return $ (nullProcessId (localNodeId node))
153 emptyPid <- return $ (nullProcessId (localNodeId node))
143 traceMsg <- return $ NCMsg {
154 traceMsg <- return $ NCMsg {
144 ctrlMsgSender = ProcessIdentifier (emptyPid)
155 ctrlMsgSender = ProcessIdentifier (emptyPid)
145− , ctrlMsgSignal = (NamedSend "logger"
156+ , ctrlMsgSignal = (NamedSend "trace.logger"
146 (createUnencodedMessage msg))
157 (createUnencodedMessage msg))
147 }
158 }
148 liftIO $ writeChan (localCtrlChan node) traceMsg
159 liftIO $ writeChan (localCtrlChan node) traceMsg
149
160
150− buildTxt :: TraceEvent -> String
161+ buildTxt :: MxEvent -> String
151− buildTxt (TraceEvLog msg) = msg
162+ buildTxt (MxLog msg) = msg
152− buildTxt ev = show ev
163+ buildTxt ev = show ev
153
164
154eventLogTracer :: Process ()
165eventLogTracer :: Process ()
155eventLogTracer =
166eventLogTracer =
@@ -158,7 +169,7 @@ eventLogTracer =
158 -- is a tracer process installed. This is just a stop gap until then.
169 -- is a tracer process installed. This is just a stop gap until then.
159 forever' $ receiveWait [ matchAny (\m -> handleMessage m writeTrace) ]
170 forever' $ receiveWait [ matchAny (\m -> handleMessage m writeTrace) ]
160 where
171 where
161− writeTrace :: TraceEvent -> Process ()
172+ writeTrace :: MxEvent -> Process ()
162 writeTrace ev = liftIO $ traceEventIO (show ev)
173 writeTrace ev = liftIO $ traceEventIO (show ev)
163
174
164logfileTracer :: FilePath -> Process ()
175logfileTracer :: FilePath -> Process ()
@@ -172,14 +183,14 @@ logfileTracer p = do
172 logger h' = forever' $ do
183 logger h' = forever' $ do
173 receiveWait [
184 receiveWait [
174 matchIf (\ev -> case ev of
185 matchIf (\ev -> case ev of
175− TraceEvDisable -> True
186+ MxTraceDisable -> True
176− (TraceEvTakeover _) -> True
187+ (MxTraceTakeover _) -> True
177 _ -> False)
188 _ -> False)
178 (\_ -> (liftIO $ hClose h') >> die "trace stopped")
189 (\_ -> (liftIO $ hClose h') >> die "trace stopped")
179 , matchAny (\ev -> handleMessage ev (writeTrace h'))
190 , matchAny (\ev -> handleMessage ev (writeTrace h'))
180 ]
191 ]
181
192
182− writeTrace :: Handle -> TraceEvent -> Process ()
193+ writeTrace :: Handle -> MxEvent -> Process ()
183 writeTrace h ev = do
194 writeTrace h ev = do
184 liftIO $ do
195 liftIO $ do
185 now <- getCurrentTime
196 now <- getCurrentTime
@@ -200,23 +211,24 @@ traceController mv = do
200 where
211 where
201 traceLoop :: TracerState -> Process ()
212 traceLoop :: TracerState -> Process ()
202 traceLoop st = do
213 traceLoop st = do
214+ let client' = client st
203− -- Trace events are forwarded to the trace target when tracing is enabled.
215+ -- Trace events are forwarded to the enabled trace target.
204 -- At some point in the future, we're going to start writing these custom
216 -- At some point in the future, we're going to start writing these custom
205 -- events to the ghc eventlog, at which point this design might change.
217 -- events to the ghc eventlog, at which point this design might change.
206 st' <- receiveWait [
218 st' <- receiveWait [
207 match (\(setResp, set :: SetTrace) -> do
219 match (\(setResp, set :: SetTrace) -> do
208− -- We allow at most one trace client, which is a process id.
220+ -- We consider at most one trace client, which is a process.
209 -- Tracking multiple clients represents too high an overhead,
221 -- Tracking multiple clients represents too high an overhead,
210− -- so we leave that kind of thing to our consumers to figure
222+ -- so we leave that kind of thing to our consumers (e.g., the
211− -- figure out.
223+ -- high level Debug client module) to figure out.
212 case set of
224 case set of
213 (TraceEnable pid) -> do
225 (TraceEnable pid) -> do
214 -- notify the previous tracer it has been replaced
226 -- notify the previous tracer it has been replaced
215− sendTrace st (createUnencodedMessage (TraceEvTakeover pid))
227+ sendTraceMsg client' (createUnencodedMessage (MxTraceTakeover pid))
216 sendOk setResp
228 sendOk setResp
217 return st { client = (Just pid) }
229 return st { client = (Just pid) }
218 TraceDisable -> do
230 TraceDisable -> do
219− sendTrace st (createUnencodedMessage TraceEvDisable)
231+ sendTraceMsg client' (createUnencodedMessage MxTraceDisable)
220 sendOk setResp
232 sendOk setResp
221 return st { client = Nothing })
233 return st { client = Nothing })
222 , match (\(confResp, flags') ->
234 , match (\(confResp, flags') ->
@@ -225,7 +237,7 @@ traceController mv = do
225 , match (\chGetCurrent -> sendChan chGetCurrent (client st) >> return st)
237 , match (\chGetCurrent -> sendChan chGetCurrent (client st) >> return st)
226 -- we dequeue incoming events even if we don't process them
238 -- we dequeue incoming events even if we don't process them
227 , matchAny (\ev ->
239 , matchAny (\ev ->
228− handleMessage ev (handleTrace st ev) >>= return . fromMaybe st)
240+ handleMessage ev (handleTrace st ev) >>= return . fromMaybe st)
229 ]
241 ]
230 traceLoop st'
242 traceLoop st'
231
243
@@ -263,8 +275,8 @@ traceController mv = do
263applyTraceFlags :: TraceFlags -> TracerState -> Process TracerState
275applyTraceFlags :: TraceFlags -> TracerState -> Process TracerState
264applyTraceFlags flags' state = return state { flags = flags' }
276applyTraceFlags flags' state = return state { flags = flags' }
265
277
266−handleTrace :: TracerState -> Message -> TraceEvent -> Process TracerState
278+handleTrace :: TracerState -> Message -> MxEvent -> Process TracerState
267−handleTrace st msg ev@(TraceEvRegistered p n) =
279+handleTrace st msg ev@(MxRegistered p n) =
268 let regNames' =
280 let regNames' =
269 Map.insertWith (\_ ns -> Set.insert n ns) p
281 Map.insertWith (\_ ns -> Set.insert n ns) p
270 (Set.singleton n)
282 (Set.singleton n)
@@ -272,7 +284,7 @@ handleTrace st msg ev@(TraceEvRegistered p n) =
272 in do
284 in do
273 traceEv ev msg (traceRegistered (flags st)) st
285 traceEv ev msg (traceRegistered (flags st)) st
274 return st { regNames = regNames' }
286 return st { regNames = regNames' }
275−handleTrace st msg ev@(TraceEvUnRegistered p n) =
287+handleTrace st msg ev@(MxUnRegistered p n) =
276 let f ns = case ns of
288 let f ns = case ns of
277 Nothing -> Nothing
289 Nothing -> Nothing
278 Just ns' -> Just (Set.delete n ns')
290 Just ns' -> Just (Set.delete n ns')
@@ -280,42 +292,42 @@ handleTrace st msg ev@(TraceEvUnRegistered p n) =
280 in do
292 in do
281 traceEv ev msg (traceUnregistered (flags st)) st
293 traceEv ev msg (traceUnregistered (flags st)) st
282 return st { regNames = regNames' }
294 return st { regNames = regNames' }
283−handleTrace st msg ev@(TraceEvSpawned _) = do
295+handleTrace st msg ev@(MxSpawned _) = do
284 traceEv ev msg (traceSpawned (flags st)) st >> return st
296 traceEv ev msg (traceSpawned (flags st)) st >> return st
285−handleTrace st msg ev@(TraceEvDied _ _) = do
297+handleTrace st msg ev@(MxProcessDied _ _) = do
286 traceEv ev msg (traceDied (flags st)) st >> return st
298 traceEv ev msg (traceDied (flags st)) st >> return st
287−handleTrace st msg ev@(TraceEvSent _ _ _) =
299+handleTrace st msg ev@(MxSent _ _ _) =
288 traceEv ev msg (traceSend (flags st)) st >> return st
300 traceEv ev msg (traceSend (flags st)) st >> return st
289−handleTrace st msg ev@(TraceEvReceived _ _) =
301+handleTrace st msg ev@(MxReceived _ _) =
290 traceEv ev msg (traceRecv (flags st)) st >> return st
302 traceEv ev msg (traceRecv (flags st)) st >> return st
291handleTrace st msg ev = do
303handleTrace st msg ev = do
292 case ev of
304 case ev of
293− (TraceEvNodeDied _ _) ->
305+ (MxNodeDied _ _) ->
294 case (traceNodes (flags st)) of
306 case (traceNodes (flags st)) of
295− True -> sendTrace st msg
307+ True -> sendTrace st ev msg
296 False -> return ()
308 False -> return ()
297− (TraceEvUser _) -> sendTrace st msg
309+ (MxUser _) -> sendTrace st ev msg
298− (TraceEvLog _) -> sendTrace st msg
310+ (MxLog _) -> sendTrace st ev msg
299 _ ->
311 _ ->
300 case (traceConnections (flags st)) of
312 case (traceConnections (flags st)) of
301− True -> sendTrace st msg
313+ True -> sendTrace st ev msg
302 False -> return ()
314 False -> return ()
303 return st
315 return st
304
316
305−traceEv :: TraceEvent
317+traceEv :: MxEvent
306 -> Message
318 -> Message
307 -> Maybe TraceSubject
319 -> Maybe TraceSubject
308 -> TracerState
320 -> TracerState
309 -> Process ()
321 -> Process ()
310traceEv _ _ Nothing _ = return ()
322traceEv _ _ Nothing _ = return ()
311−traceEv _ msg (Just TraceAll) st = sendTrace st msg
323+traceEv ev msg (Just TraceAll) st = sendTrace st ev msg
312traceEv ev msg (Just (TraceProcs pids)) st = do
324traceEv ev msg (Just (TraceProcs pids)) st = do
313 node <- processNode <$> ask
325 node <- processNode <$> ask
314 let p = case resolveToPid ev of
326 let p = case resolveToPid ev of
315 Nothing -> (nullProcessId (localNodeId node))
327 Nothing -> (nullProcessId (localNodeId node))
316 Just pid -> pid
328 Just pid -> pid
317 case (Set.member p pids) of
329 case (Set.member p pids) of
318− True -> sendTrace st msg
330+ True -> sendTrace st ev msg
319 False -> return ()
331 False -> return ()
320traceEv ev msg (Just (TraceNames names)) st = do
332traceEv ev msg (Just (TraceNames names)) st = do
321 -- if we have recorded regnames for p, then we forward the trace iif
333 -- if we have recorded regnames for p, then we forward the trace iif
@@ -328,13 +340,15 @@ traceEv ev msg (Just (TraceNames names)) st = do
328 Nothing -> return ()
340 Nothing -> return ()
329 Just ns -> if (Set.null (Set.intersection ns names))
341 Just ns -> if (Set.null (Set.intersection ns names))
330 then return ()
342 then return ()
331− else sendTrace st msg
343+ else sendTrace st ev msg
332−
333−sendTrace :: TracerState -> Message -> Process ()
334−sendTrace st msg =
335− let pid = (client st) in do
336− case pid of
337− Just p -> (flip forward) p msg
338− Nothing -> return ()
339
344
345+sendTrace :: TracerState -> MxEvent -> Message -> Process ()
346+sendTrace st ev msg = do
347+ let c = client st
348+ if c == (resolveToPid ev) -- we do not send the tracer events about itself...
349+ then return ()
350+ else sendTraceMsg c msg
340
351
352+sendTraceMsg :: Maybe ProcessId -> Message -> Process ()
353+sendTraceMsg Nothing _ = return ()
354+sendTraceMsg (Just p) msg = (flip forward) p msg
src/Control/Distributed/Process/Management/Internal/Trace/Types.hsnew file+159
@@ -0,0 +1,159 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE ExistentialQuantification #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE TypeSynonymInstances #-}
5+{-# LANGUAGE GADTs #-}
6+{-# LANGUAGE DeriveGeneric #-}
7+
8+-- | Tracing/Debugging support - Types
9+module Control.Distributed.Process.Management.Internal.Trace.Types
10+ ( SetTrace(..)
11+ , TraceSubject(..)
12+ , TraceFlags(..)
13+ , TraceArg(..)
14+ , TraceOk(..)
15+ , traceLog
16+ , traceLogFmt
17+ , traceEvent
18+ , traceMessage
19+ , defaultTraceFlags
20+ , enableTrace
21+ , enableTraceSync
22+ , disableTrace
23+ , disableTraceSync
24+ , getTraceFlags
25+ , setTraceFlags
26+ , setTraceFlagsSync
27+ , getCurrentTraceClient
28+ ) where
29+
30+import Control.Distributed.Process.Internal.Types
31+ ( MxEventBus(..)
32+ , ProcessId
33+ , SendPort
34+ , unsafeCreateUnencodedMessage
35+ )
36+import Control.Distributed.Process.Management.Internal.Bus
37+ ( publishEvent
38+ )
39+import Control.Distributed.Process.Management.Internal.Types
40+ ( MxEvent(..)
41+ )
42+import Control.Distributed.Process.Serializable
43+import Data.Binary
44+import Data.List (intersperse)
45+import Data.Set (Set)
46+import Data.Typeable
47+import GHC.Generics
48+
49+--------------------------------------------------------------------------------
50+-- Types --
51+--------------------------------------------------------------------------------
52+
53+data SetTrace = TraceEnable !ProcessId | TraceDisable
54+ deriving (Typeable, Generic, Eq, Show)
55+instance Binary SetTrace where
56+
57+-- | Defines which processes will be traced by a given 'TraceFlag',
58+-- either by name, or @ProcessId@. Choosing @TraceAll@ is /by far/
59+-- the most efficient approach, as the tracer process therefore
60+-- avoids deciding whether or not a trace event is viable.
61+--
62+data TraceSubject =
63+ TraceAll -- enable tracing for all running processes
64+ | TraceProcs !(Set ProcessId) -- enable tracing for a set of processes
65+ | TraceNames !(Set String) -- enable tracing for a set of named/registered processes
66+ deriving (Typeable, Generic, Show)
67+instance Binary TraceSubject where
68+
69+-- | Defines /what/ will be traced. Flags that control tracing of
70+-- @Process@ events, take a 'TraceSubject' controlling which processes
71+-- should generate trace events in the target process.
72+data TraceFlags = TraceFlags {
73+ traceSpawned :: !(Maybe TraceSubject) -- filter process spawned tracing
74+ , traceDied :: !(Maybe TraceSubject) -- filter process died tracing
75+ , traceRegistered :: !(Maybe TraceSubject) -- filter process registration tracing
76+ , traceUnregistered :: !(Maybe TraceSubject) -- filter process un-registration
77+ , traceSend :: !(Maybe TraceSubject) -- filter process/message tracing by sender
78+ , traceRecv :: !(Maybe TraceSubject) -- filter process/message tracing by receiver
79+ , traceNodes :: !Bool -- enable node status trace events
80+ , traceConnections :: !Bool -- enable connection status trace events
81+ } deriving (Typeable, Generic, Show)
82+instance Binary TraceFlags where
83+
84+defaultTraceFlags :: TraceFlags
85+defaultTraceFlags =
86+ TraceFlags {
87+ traceSpawned = Nothing
88+ , traceDied = Nothing
89+ , traceRegistered = Nothing
90+ , traceUnregistered = Nothing
91+ , traceSend = Nothing
92+ , traceRecv = Nothing
93+ , traceNodes = False
94+ , traceConnections = False
95+ }
96+
97+data TraceArg =
98+ TraceStr String
99+ | forall a. (Show a) => Trace a
100+
101+-- | A generic 'ok' response from the trace coordinator.
102+data TraceOk = TraceOk
103+ deriving (Typeable, Generic)
104+instance Binary TraceOk where
105+
106+--------------------------------------------------------------------------------
107+-- Internal/Common API --
108+--------------------------------------------------------------------------------
109+
110+traceLog :: MxEventBus -> String -> IO ()
111+traceLog tr s = publishEvent tr (unsafeCreateUnencodedMessage $ MxLog s)
112+
113+traceLogFmt :: MxEventBus
114+ -> String
115+ -> [TraceArg]
116+ -> IO ()
117+traceLogFmt t d ls =
118+ traceLog t $ concat (intersperse d (map toS ls))
119+ where toS :: TraceArg -> String
120+ toS (TraceStr s) = s
121+ toS (Trace a) = show a
122+
123+traceEvent :: MxEventBus -> MxEvent -> IO ()
124+traceEvent tr ev = publishEvent tr (unsafeCreateUnencodedMessage ev)
125+
126+traceMessage :: Serializable m => MxEventBus -> m -> IO ()
127+traceMessage tr msg = traceEvent tr (MxUser (unsafeCreateUnencodedMessage msg))
128+
129+enableTrace :: MxEventBus -> ProcessId -> IO ()
130+enableTrace t p =
131+ publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)),
132+ (TraceEnable p)))
133+
134+enableTraceSync :: MxEventBus -> SendPort TraceOk -> ProcessId -> IO ()
135+enableTraceSync t s p =
136+ publishEvent t (unsafeCreateUnencodedMessage (Just s, TraceEnable p))
137+
138+disableTrace :: MxEventBus -> IO ()
139+disableTrace t =
140+ publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)),
141+ TraceDisable))
142+
143+disableTraceSync :: MxEventBus -> SendPort TraceOk -> IO ()
144+disableTraceSync t s =
145+ publishEvent t (unsafeCreateUnencodedMessage ((Just s), TraceDisable))
146+
147+setTraceFlags :: MxEventBus -> TraceFlags -> IO ()
148+setTraceFlags t f =
149+ publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)), f))
150+
151+setTraceFlagsSync :: MxEventBus -> SendPort TraceOk -> TraceFlags -> IO ()
152+setTraceFlagsSync t s f =
153+ publishEvent t (unsafeCreateUnencodedMessage ((Just s), f))
154+
155+getTraceFlags :: MxEventBus -> SendPort TraceFlags -> IO ()
156+getTraceFlags t s = publishEvent t (unsafeCreateUnencodedMessage s)
157+
158+getCurrentTraceClient :: MxEventBus -> SendPort (Maybe ProcessId) -> IO ()
159+getCurrentTraceClient t s = publishEvent t (unsafeCreateUnencodedMessage s)
src/Control/Distributed/Process/Management/Internal/Types.hsnew file+141
@@ -0,0 +1,141 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE ExistentialQuantification #-}
3+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE DeriveGeneric #-}
5+module Control.Distributed.Process.Management.Internal.Types
6+ ( MxAgentId(..)
7+ , MxAgentState(..)
8+ , MxAgent(..)
9+ , MxAction(..)
10+ , ChannelSelector(..)
11+ , Fork
12+ , MxSink
13+ , MxEvent(..)
14+ , Addressable(..)
15+ ) where
16+
17+import Control.Applicative (Applicative)
18+import Control.Concurrent.STM
19+ ( TChan
20+ )
21+import Control.Distributed.Process.Internal.Types
22+ ( Process
23+ , ProcessId
24+ , SendPortId
25+ , Message
26+ , DiedReason
27+ , NodeId
28+ )
29+import Control.Monad.IO.Class (MonadIO)
30+import qualified Control.Monad.State as ST
31+ ( MonadState
32+ , StateT
33+ )
34+import Control.Monad.Fix (MonadFix)
35+import Data.Binary
36+import Data.Typeable (Typeable)
37+import GHC.Generics
38+import Network.Transport
39+ ( ConnectionId
40+ , EndPointAddress
41+ )
42+
43+-- | This is the /default/ management event, fired for various internal
44+-- events around the NT connection and Process lifecycle. All published
45+-- events that conform to this type, are eligible for tracing - i.e.,
46+-- they will be delivered to the trace controller.
47+--
48+data MxEvent =
49+ MxSpawned ProcessId
50+ -- ^ fired whenever a local process is spawned
51+ | MxRegistered ProcessId String
52+ -- ^ fired whenever a process/name is registered (locally)
53+ | MxUnRegistered ProcessId String
54+ -- ^ fired whenever a process/name is unregistered (locally)
55+ | MxProcessDied ProcessId DiedReason
56+ -- ^ fired whenever a process dies
57+ | MxNodeDied NodeId DiedReason
58+ -- ^ fired whenever a node /dies/ (i.e., the connection is broken/disconnected)
59+ | MxSent ProcessId ProcessId Message
60+ -- ^ 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
65+ | MxReceived ProcessId Message
66+ -- ^ 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
69+ | MxConnected ConnectionId EndPointAddress
70+ -- ^ fired when a network-transport connection is first established
71+ | MxDisconnected ConnectionId EndPointAddress
72+ -- ^ fired when a network-transport connection is broken/disconnected
73+ | MxUser Message
74+ -- ^ a user defined trace event
75+ | MxLog String
76+ -- ^ a /logging/ event - used for debugging purposes only
77+ | MxTraceTakeover ProcessId
78+ -- ^ notifies a trace listener that all subsequent traces will be sent to /pid/
79+ | MxTraceDisable
80+ -- ^ notifies a trace listener that it has been disabled/removed
81+ deriving (Typeable, Generic, Show)
82+
83+instance Binary MxEvent where
84+
85+-- | The class of things that we might be able to resolve to
86+-- a @ProcessId@ (or not).
87+class Addressable a where
88+ resolveToPid :: a -> Maybe ProcessId
89+
90+instance Addressable MxEvent where
91+ resolveToPid (MxSpawned p) = Just p
92+ resolveToPid (MxProcessDied p _) = Just p
93+ resolveToPid (MxSent _ p _) = Just p
94+ resolveToPid (MxReceived p _) = Just p
95+ resolveToPid _ = Nothing
96+
97+-- | Gross though it is, this synonym represents a function
98+-- used to forking new processes, which has to be passed as a HOF
99+-- when calling mxAgentController, since there's no other way to
100+-- avoid a circular dependency with Node.hs
101+type Fork = (Process () -> IO ProcessId)
102+
103+-- | A newtype wrapper for an agent id (which is a string).
104+newtype MxAgentId = MxAgentId { agentId :: String }
105+ deriving (Typeable, Binary, Eq, Ord)
106+
107+data MxAgentState s = MxAgentState
108+ {
109+ mxAgentId :: !MxAgentId
110+ , mxBus :: !(TChan Message)
111+ , mxLocalState :: !s
112+ }
113+
114+-- | Monad for management agents.
115+--
116+newtype MxAgent s a =
117+ MxAgent
118+ {
119+ unAgent :: ST.StateT (MxAgentState s) Process a
120+ } deriving ( Functor
121+ , Monad
122+ , MonadIO
123+ , MonadFix
124+ , ST.MonadState (MxAgentState s)
125+ , Typeable
126+ , Applicative
127+ )
128+
129+data ChannelSelector = InputChan | Mailbox
130+
131+-- | Represents the actions a management agent can take
132+-- when evaluating an /event sink/.
133+--
134+data MxAction =
135+ MxAgentDeactivate !String
136+ | MxAgentPrioritise !ChannelSelector
137+ | MxAgentReady
138+ | MxAgentSkip
139+
140+-- | Type of a management agent's event sink.
141+type MxSink s = Message -> MxAgent s (Maybe MxAction)
src/Control/Distributed/Process/Node.hs+378−216
@@ -1,3 +1,12 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveDataTypeable #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+{-# LANGUAGE RankNTypes #-}
5+{-# LANGUAGE BangPatterns #-}
6+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7+{-# LANGUAGE RecordWildCards #-}
8+{-# LANGUAGE MagicHash #-}
9+
1-- | Local nodes
10-- | Local nodes
2--
11--
3module Control.Distributed.Process.Node
12module Control.Distributed.Process.Node
@@ -12,10 +21,6 @@ module Control.Distributed.Process.Node
12
21
13-- TODO: Calls to 'sendBinary' and co (by the NC) may stall the node controller.
22-- TODO: Calls to 'sendBinary' and co (by the NC) may stall the node controller.
14
23
15−#if ! MIN_VERSION_base(4,6,0)
16−import Prelude hiding (catch)
17−#endif
18−
19import System.IO (fixIO, hPutStrLn, stderr)
24import System.IO (fixIO, hPutStrLn, stderr)
20import System.Mem.Weak (Weak, deRefWeak)
25import System.Mem.Weak (Weak, deRefWeak)
21import qualified Data.ByteString.Lazy as BSL (fromChunks)
26import qualified Data.ByteString.Lazy as BSL (fromChunks)
@@ -23,46 +28,67 @@ import Data.Binary (decode)
23import Data.Map (Map)
28import Data.Map (Map)
24import qualified Data.Map as Map
29import qualified Data.Map as Map
25 ( empty
30 ( empty
31+ , delete
26 , toList
32 , toList
27 , fromList
33 , fromList
28− , filter
34+ , insert
35+ , lookup
36+ , partition
29 , partitionWithKey
37 , partitionWithKey
30 , elems
38 , elems
39+ , size
31 , filterWithKey
40 , filterWithKey
32 , foldlWithKey
41 , foldlWithKey
33 )
42 )
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
34import Data.Set (Set)
49import Data.Set (Set)
35import qualified Data.Set as Set
50import qualified Data.Set as Set
36 ( empty
51 ( empty
37 , insert
52 , insert
38 , delete
53 , delete
54+ , map
39 , member
55 , member
40 , toList
56 , toList
57+ , union
41 )
58 )
42import Data.Foldable (forM_)
59import Data.Foldable (forM_)
43import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
60import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
44import Data.Typeable (Typeable)
61import Data.Typeable (Typeable)
45import Control.Category ((>>>))
62import Control.Category ((>>>))
46−import Control.Applicative ((<$>))
63+import Control.Applicative
47−import Control.Monad (void, when)
64+import Control.Monad (void, when, join)
48import Control.Monad.IO.Class (MonadIO, liftIO)
65import Control.Monad.IO.Class (MonadIO, liftIO)
49import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
66import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
50import qualified Control.Monad.State.Strict as StateT (get, put)
67import qualified Control.Monad.State.Strict as StateT (get, put)
51import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
68import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
52import Control.Exception
69import Control.Exception
53 ( throwIO
70 ( throwIO
54− , AsyncException(ThreadKilled)
55 , SomeException
71 , SomeException
56 , Exception
72 , Exception
57 , throwTo
73 , throwTo
74+ , uninterruptibleMask_
75+ , getMaskingState
76+ , MaskingState(..)
58 )
77 )
59−import qualified Control.Exception as Exception (Handler(..), catch, catches, finally)
78+import qualified Control.Exception as Exception
79+ ( Handler(..)
80+ , catch
81+ , catches
82+ , finally
83+ )
60−import Control.Concurrent (forkIO, myThreadId)
84+import Control.Concurrent (forkIO, killThread)
85+import Control.Distributed.Process.Internal.BiMultiMap (BiMultiMap)
86+import qualified Control.Distributed.Process.Internal.BiMultiMap as BiMultiMap
61import Control.Distributed.Process.Internal.StrictMVar
87import Control.Distributed.Process.Internal.StrictMVar
62 ( newMVar
88 ( newMVar
63 , withMVar
89 , withMVar
90+ , modifyMVarMasked
64 , modifyMVar
91 , modifyMVar
65− , modifyMVar_
66 , newEmptyMVar
92 , newEmptyMVar
67 , putMVar
93 , putMVar
68 , takeMVar
94 , takeMVar
@@ -77,6 +103,7 @@ import Control.Distributed.Process.Internal.CQueue
77 , enqueue
103 , enqueue
78 , newCQueue
104 , newCQueue
79 , mkWeakCQueue
105 , mkWeakCQueue
106+ , queueSize
80 )
107 )
81import qualified Network.Transport as NT
108import qualified Network.Transport as NT
82 ( Transport
109 ( Transport
@@ -88,8 +115,8 @@ import qualified Network.Transport as NT
88 , TransportError(..)
115 , TransportError(..)
89 , address
116 , address
90 , closeEndPoint
117 , closeEndPoint
91− , ConnectionId
92 , Connection
118 , Connection
119+ , ConnectionId
93 , close
120 , close
94 , EndPointAddress
121 , EndPointAddress
95 , Reliability(ReliableOrdered)
122 , Reliability(ReliableOrdered)
@@ -108,6 +135,9 @@ import Control.Distributed.Process.Internal.Types
108 , LocalNode(..)
135 , LocalNode(..)
109 , MxEventBus(..)
136 , MxEventBus(..)
110 , LocalNodeState(..)
137 , LocalNodeState(..)
138+ , ValidLocalNodeState(..)
139+ , withValidLocalState
140+ , modifyValidLocalState
111 , LocalProcess(..)
141 , LocalProcess(..)
112 , LocalProcessState(..)
142 , LocalProcessState(..)
113 , Process(..)
143 , Process(..)
@@ -117,9 +147,11 @@ import Control.Distributed.Process.Internal.Types
117 , localPidCounter
147 , localPidCounter
118 , localPidUnique
148 , localPidUnique
119 , localProcessWithId
149 , localProcessWithId
150+ , localProcesses
120 , localConnections
151 , localConnections
121 , forever'
152 , forever'
122 , MonitorRef(..)
153 , MonitorRef(..)
154+ , NodeClosedException(..)
123 , ProcessMonitorNotification(..)
155 , ProcessMonitorNotification(..)
124 , NodeMonitorNotification(..)
156 , NodeMonitorNotification(..)
125 , PortMonitorNotification(..)
157 , PortMonitorNotification(..)
@@ -139,44 +171,52 @@ import Control.Distributed.Process.Internal.Types
139 , nodeOf
171 , nodeOf
140 , ProcessInfo(..)
172 , ProcessInfo(..)
141 , ProcessInfoNone(..)
173 , ProcessInfoNone(..)
174+ , NodeStats(..)
142 , SendPortId(..)
175 , SendPortId(..)
143 , typedChannelWithId
176 , typedChannelWithId
144 , RegisterReply(..)
177 , RegisterReply(..)
145 , WhereIsReply(..)
178 , WhereIsReply(..)
146 , payloadToMessage
179 , payloadToMessage
147 , createUnencodedMessage
180 , createUnencodedMessage
181+ , unsafeCreateUnencodedMessage
148 , runLocalProcess
182 , runLocalProcess
149 , firstNonReservedProcessId
183 , firstNonReservedProcessId
150 , ImplicitReconnect(WithImplicitReconnect)
184 , ImplicitReconnect(WithImplicitReconnect)
151 )
185 )
186+import Control.Distributed.Process.Management.Internal.Agent
187+ ( mxAgentController
188+ )
189+import Control.Distributed.Process.Management.Internal.Types
190+ ( MxEvent(..)
191+ )
152−import qualified Control.Distributed.Process.Internal.Trace.Remote as Trace
192+import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
153 ( remoteTable
193 ( remoteTable
154 )
194 )
155−import Control.Distributed.Process.Internal.Trace.Types
195+import Control.Distributed.Process.Management.Internal.Trace.Tracer
196+ ( defaultTracer
197+ )
198+import Control.Distributed.Process.Management.Internal.Trace.Types
156 ( TraceArg(..)
199 ( TraceArg(..)
157− , TraceEvent(..)
158 , traceEvent
200 , traceEvent
159 , traceLogFmt
201 , traceLogFmt
160 , enableTrace
202 , enableTrace
161 )
203 )
162−import Control.Distributed.Process.Internal.Trace.Tracer
163− ( defaultTracer
164− )
165import Control.Distributed.Process.Serializable (Serializable)
204import Control.Distributed.Process.Serializable (Serializable)
166import Control.Distributed.Process.Internal.Messaging
205import Control.Distributed.Process.Internal.Messaging
167 ( sendBinary
206 ( sendBinary
168− , sendMessage
169 , closeImplicitReconnections
207 , closeImplicitReconnections
170 , impliesDeathOf
208 , impliesDeathOf
171 )
209 )
172import Control.Distributed.Process.Internal.Primitives
210import Control.Distributed.Process.Internal.Primitives
173 ( register
211 ( register
174− , finally
175 , receiveWait
212 , receiveWait
176 , match
213 , match
177 , sendChan
214 , sendChan
178− , catch
179 , unwrapMessage
215 , unwrapMessage
216+ , monitor
217+ , unmonitorAsync
218+ , getSelfNode
219+ , SayMessage(..)
180 )
220 )
181import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
221import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
182import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
222import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
@@ -185,11 +225,20 @@ import qualified Control.Distributed.Process.Internal.StrictContainerAccessors a
185 ( mapMaybe
225 ( mapMaybe
186 , mapDefault
226 , mapDefault
187 )
227 )
188−import Control.Distributed.Process.Management.Agent
228+import Control.Monad.Catch (try)
189− ( mxAgentController
229+import GHC.IO (IO(..), unsafeUnmask)
190− )
230+import GHC.Base ( maskAsyncExceptions# )
191−import System.Environment (getEnv)
231+
192import Unsafe.Coerce
232import 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
193
242
194--------------------------------------------------------------------------------
243--------------------------------------------------------------------------------
195-- Initialization --
244-- Initialization --
@@ -213,7 +262,7 @@ newLocalNode transport rtable = do
213createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
262createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
214createBareLocalNode endPoint rtable = do
263createBareLocalNode endPoint rtable = do
215 unq <- randomIO
264 unq <- randomIO
216− state <- newMVar LocalNodeState
265+ state <- newMVar $ LocalNodeValid $ ValidLocalNodeState
217 { _localProcesses = Map.empty
266 { _localProcesses = Map.empty
218 , _localPidCounter = firstNonReservedProcessId
267 , _localPidCounter = firstNonReservedProcessId
219 , _localPidUnique = unq
268 , _localPidUnique = unq
@@ -251,8 +300,8 @@ startMxAgent node = do
251 let fork = forkProcess node
300 let fork = forkProcess node
252 mv <- MVar.newEmptyMVar
301 mv <- MVar.newEmptyMVar
253 pid <- fork $ mxAgentController fork mv
302 pid <- fork $ mxAgentController fork mv
254− (tracer, wqRef, mxNew) <- MVar.takeMVar mv
303+ (tracer', wqRef, mxNew') <- MVar.takeMVar mv
255− return node { localEventBus = (MxEventBus pid tracer wqRef mxNew) }
304+ return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }
256
305
257startDefaultTracer :: LocalNode -> IO ()
306startDefaultTracer :: LocalNode -> IO ()
258startDefaultTracer node' = do
307startDefaultTracer node' = do
@@ -265,6 +314,8 @@ startDefaultTracer node' = do
265 runProcess node' $ register "tracer.initial" pid'
314 runProcess node' $ register "tracer.initial" pid'
266 _ -> return ()
315 _ -> return ()
267
316
317+-- TODO: we need a better mechanism for defining and registering services
318+
268-- | Start and register the service processes on a node
319-- | Start and register the service processes on a node
269startServiceProcesses :: LocalNode -> IO ()
320startServiceProcesses :: LocalNode -> IO ()
270startServiceProcesses node = do
321startServiceProcesses node = do
@@ -274,47 +325,69 @@ startServiceProcesses node = do
274 -- however, so we deliberably ignore it
325 -- however, so we deliberably ignore it
275 startDefaultTracer node
326 startDefaultTracer node
276 logger <- forkProcess node loop
327 logger <- forkProcess node loop
328+ runProcess node $ do
329+ register "logger" logger
330+ -- The trace.logger is used for tracing to the console to avoid feedback
331+ -- loops during tracing if the user reregisters the "logger" with a custom
332+ -- process which uses 'send' or other primitives which are traced.
277− runProcess node $ register "logger" logger
333+ register "trace.logger" logger
278 where
334 where
279− loop = do
335+ loop = do
280− receiveWait
336+ receiveWait
281− [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
337+ [ match $ \(SayMessage time pid string) -> do
338+ let time' = formatTime defaultTimeLocale "%c" time
282− liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
339+ liftIO . hPutStrLn stderr $ time' ++ " " ++ show pid ++ ": " ++ string
283− loop
340+ loop
284− , match $ \((time, string) :: (String, String)) -> do
341+ , match $ \((time, string) :: (String, String)) -> do
285− -- this is a 'trace' message from the local node tracer
342+ -- this is a 'trace' message from the local node tracer
286− liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
343+ liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
287− loop
344+ loop
288− , match $ \(ch :: SendPort ()) -> -- a shutdown request
345+ , match $ \(ch :: SendPort ()) -> -- a shutdown request
289− sendChan ch ()
346+ sendChan ch ()
290− ]
347+ ]
291−
348+
292−-- | Force-close a local node
349+-- | Force-close a local node, killing all processes on that node.
293−--
294−-- TODO: for now we just close the associated endpoint
295closeLocalNode :: LocalNode -> IO ()
350closeLocalNode :: LocalNode -> IO ()
296−closeLocalNode node =
351+closeLocalNode node = do
297− -- TODO: close all our processes, surely!?
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').
298 NT.closeEndPoint (localEndPoint node)
368 NT.closeEndPoint (localEndPoint node)
299
369
300-- | Run a process on a local node and wait for it to finish
370-- | Run a process on a local node and wait for it to finish
301runProcess :: LocalNode -> Process () -> IO ()
371runProcess :: LocalNode -> Process () -> IO ()
302runProcess node proc = do
372runProcess node proc = do
303 done <- newEmptyMVar
373 done <- newEmptyMVar
304− tid <- myThreadId
374+ -- TODO; When forkProcess inherits the masking state, protect the forked
375+ -- thread against async exceptions that could occur before 'try' is evaluated.
305− void $ forkProcess node $ do
376+ void $ forkProcess node $ try proc >>= liftIO . putMVar done
306− catch (proc `finally` liftIO (putMVar done ()))
377+ takeMVar done >>= either (throwIO :: SomeException -> IO a) return
307− (\(ex :: SomeException) -> liftIO $ throwTo tid ex)
308− takeMVar done
309
378
310-- | Spawn a new process on a local node
379-- | Spawn a new process on a local node
311forkProcess :: LocalNode -> Process () -> IO ProcessId
380forkProcess :: LocalNode -> Process () -> IO ProcessId
381+forkProcess node proc = do
382+ ms <- getMaskingState
312−forkProcess node proc = modifyMVar (localState node) startProcess
383+ modifyMVarMasked (localState node) (startProcess ms)
313 where
384 where
385+ startProcess :: MaskingState
386+ -> LocalNodeState
314− startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
387+ -> IO (LocalNodeState, ProcessId)
315− startProcess st = do
388+ startProcess ms (LocalNodeValid vst) = do
316− let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
389+ let lpid = LocalProcessId { lpidCounter = vst ^. localPidCounter
317− , lpidUnique = st ^. localPidUnique
390+ , lpidUnique = vst ^. localPidUnique
318 }
391 }
319 let pid = ProcessId { processNodeId = localNodeId node
392 let pid = ProcessId { processNodeId = localNodeId node
320 , processLocalId = lpid
393 , processLocalId = lpid
@@ -334,9 +407,15 @@ forkProcess node proc = modifyMVar (localState node) startProcess
334 , processThread = tid
407 , processThread = tid
335 , processNode = node
408 , processNode = node
336 }
409 }
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
337− tid' <- forkIO $ do
416+ tid' <- uninterruptibleMask_ $ forkIO $ do
338 reason <- Exception.catches
417 reason <- Exception.catches
339− (runLocalProcess lproc proc >> return DiedNormal)
418+ (unmask $ runLocalProcess lproc proc >> return DiedNormal)
340 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
419 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
341 mMsg <- unwrapMessage msg :: IO (Maybe String)
420 mMsg <- unwrapMessage msg :: IO (Maybe String)
342 case mMsg of
421 case mMsg of
@@ -346,41 +425,54 @@ forkProcess node proc = modifyMVar (localState node) startProcess
346 (return . DiedException . (show :: SomeException -> String)))]
425 (return . DiedException . (show :: SomeException -> String)))]
347
426
348 -- [Unified: Table 4, rules termination and exiting]
427 -- [Unified: Table 4, rules termination and exiting]
349− 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+
350 writeChan (localCtrlChan node) NCMsg
434 writeChan (localCtrlChan node) NCMsg
351 { ctrlMsgSender = ProcessIdentifier pid
435 { ctrlMsgSender = ProcessIdentifier pid
352 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
436 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
353 }
437 }
354 return (tid', lproc)
438 return (tid', lproc)
355
439
356−
357 -- see note [tracer/forkProcess races]
440 -- see note [tracer/forkProcess races]
358− trace node (TraceEvSpawned pid)
441+ trace node (MxSpawned pid)
359
442
360 if lpidCounter lpid == maxBound
443 if lpidCounter lpid == maxBound
361 then do
444 then do
445+ -- TODO: this doesn't look right at all - how do we know
446+ -- that newUnique represents a process id that is available!?
362 newUnique <- randomIO
447 newUnique <- randomIO
448+ return ( LocalNodeValid
363− return ( (localProcessWithId lpid ^= Just lproc)
449+ $ (localProcessWithId lpid ^= Just lproc)
364 . (localPidCounter ^= firstNonReservedProcessId)
450 . (localPidCounter ^= firstNonReservedProcessId)
365 . (localPidUnique ^= newUnique)
451 . (localPidUnique ^= newUnique)
366− $ st
452+ $ vst
367 , pid
453 , pid
368 )
454 )
369 else
455 else
456+ return ( LocalNodeValid
370− return ( (localProcessWithId lpid ^= Just lproc)
457+ $ (localProcessWithId lpid ^= Just lproc)
371 . (localPidCounter ^: (+ 1))
458 . (localPidCounter ^: (+ 1))
372− $ st
459+ $ vst
373 , pid
460 , pid
374 )
461 )
462+ startProcess _ LocalNodeClosed =
463+ throwIO $ NodeClosedException $ localNodeId node
375
464
376− cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
465+ cleanupProcess :: ProcessId
466+ -> ValidLocalNodeState
467+ -> IO (ValidLocalNodeState, [NT.Connection])
377− cleanupProcess pid st = do
468+ cleanupProcess pid vst = do
378 let pid' = ProcessIdentifier pid
469 let pid' = ProcessIdentifier pid
379− let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
470+ let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (vst ^. localConnections)
380− mapM_ (NT.close . fst) (Map.elems affected)
381− return $ (localProcessWithId (processLocalId pid) ^= Nothing)
471+ return ( (localProcessWithId (processLocalId pid) ^= Nothing)
382− . (localConnections ^= unaffected)
472+ . (localConnections ^= unaffected)
383− $ st
473+ $ vst
474+ , map fst $ Map.elems affected
475+ )
384
476
385-- note [tracer/forkProcess races]
477-- note [tracer/forkProcess races]
386--
478--
@@ -400,7 +492,7 @@ type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
400data IncomingTarget =
492data IncomingTarget =
401 Uninit
493 Uninit
402 | ToProc ProcessId (Weak (CQueue Message))
494 | ToProc ProcessId (Weak (CQueue Message))
403− | ToChan TypedChannel
495+ | ToChan SendPortId TypedChannel
404 | ToNode
496 | ToNode
405
497
406data ConnectionState = ConnectionState {
498data ConnectionState = ConnectionState {
@@ -427,6 +519,7 @@ incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
427
519
428handleIncomingMessages :: LocalNode -> IO ()
520handleIncomingMessages :: LocalNode -> IO ()
429handleIncomingMessages node = go initConnectionState
521handleIncomingMessages node = go initConnectionState
522+ `Exception.catch` \(NodeClosedException _) -> return ()
430 where
523 where
431 go :: ConnectionState -> IO ()
524 go :: ConnectionState -> IO ()
432 go !st = do
525 go !st = do
@@ -435,13 +528,14 @@ handleIncomingMessages node = go initConnectionState
435 NT.ConnectionOpened cid rel theirAddr ->
528 NT.ConnectionOpened cid rel theirAddr ->
436 if rel == NT.ReliableOrdered
529 if rel == NT.ReliableOrdered
437 then
530 then
438− trace node (TraceEvConnected cid)
531+ trace node (MxConnected cid theirAddr)
439 >> go (
532 >> go (
440 (incomingAt cid ^= Just (theirAddr, Uninit))
533 (incomingAt cid ^= Just (theirAddr, Uninit))
441 . (incomingFrom theirAddr ^: Set.insert cid)
534 . (incomingFrom theirAddr ^: Set.insert cid)
442 $ st
535 $ st
443 )
536 )
444− else invalidRequest cid st
537+ else invalidRequest cid st $
538+ "attempt to connect with unsupported reliability " ++ show rel
445 NT.Received cid payload ->
539 NT.Received cid payload ->
446 case st ^. incomingAt cid of
540 case st ^. incomingAt cid of
447 Just (_, ToProc pid weakQueue) -> do
541 Just (_, ToProc pid weakQueue) -> do
@@ -451,15 +545,19 @@ handleIncomingMessages node = go initConnectionState
451 -- it from the NC state? (and same for channels, below)
545 -- it from the NC state? (and same for channels, below)
452 let msg = payloadToMessage payload
546 let msg = payloadToMessage payload
453 enqueue queue msg -- 'enqueue' is strict
547 enqueue queue msg -- 'enqueue' is strict
454− trace node (TraceEvReceived pid msg)
548+ trace node (MxReceived pid msg)
455 go st
549 go st
456− Just (_, ToChan (TypedChannel chan')) -> do
550+ Just (_, ToChan chId (TypedChannel chan')) -> do
457 mChan <- deRefWeak chan'
551 mChan <- deRefWeak chan'
458 -- If mChan is Nothing, the process has given up the read end of
552 -- If mChan is Nothing, the process has given up the read end of
459 -- the channel and we simply ignore the incoming message
553 -- the channel and we simply ignore the incoming message
460− forM_ mChan $ \chan -> atomically $
554+ forM_ mChan $ \chan -> do
555+ msg' <- atomically $ do
556+ msg <- return $! decode (BSL.fromChunks payload)
461− -- We make sure the message is fully decoded when it is enqueued
557+ -- We make sure the message is fully decoded when it is enqueued
462− writeTQueue chan $! decode (BSL.fromChunks payload)
558+ writeTQueue chan msg
559+ return msg
560+ trace node $ MxReceivedPort chId $ unsafeCreateUnencodedMessage msg'
463 go st
561 go st
464 Just (_, ToNode) -> do
562 Just (_, ToNode) -> do
465 let ctrlMsg = decode . BSL.fromChunks $ payload
563 let ctrlMsg = decode . BSL.fromChunks $ payload
@@ -469,38 +567,49 @@ handleIncomingMessages node = go initConnectionState
469 case decode (BSL.fromChunks payload) of
567 case decode (BSL.fromChunks payload) of
470 ProcessIdentifier pid -> do
568 ProcessIdentifier pid -> do
471 let lpid = processLocalId pid
569 let lpid = processLocalId pid
472− mProc <- withMVar state $ return . (^. localProcessWithId lpid)
570+ mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
473 case mProc of
571 case mProc of
474 Just proc ->
572 Just proc ->
475 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
573 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
476 Nothing ->
574 Nothing ->
477− invalidRequest cid st
575+ -- incoming attempt to connect to unknown process - might
576+ -- be dead already
577+ go (incomingAt cid ^= Nothing $ st)
478 SendPortIdentifier chId -> do
578 SendPortIdentifier chId -> do
479 let lcid = sendPortLocalId chId
579 let lcid = sendPortLocalId chId
480 lpid = processLocalId (sendPortProcessId chId)
580 lpid = processLocalId (sendPortProcessId chId)
481− mProc <- withMVar state $ return . (^. localProcessWithId lpid)
581+ mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
482 case mProc of
582 case mProc of
483 Just proc -> do
583 Just proc -> do
484 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
584 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
485 case mChannel of
585 case mChannel of
486 Just channel ->
586 Just channel ->
487− go (incomingAt cid ^= Just (src, ToChan channel) $ st)
587+ go (incomingAt cid ^= Just (src, ToChan chId channel) $ st)
488 Nothing ->
588 Nothing ->
489− invalidRequest cid st
589+ invalidRequest cid st $
590+ "incoming attempt to connect to unknown channel of"
591+ ++ " process " ++ show (sendPortProcessId chId)
490 Nothing ->
592 Nothing ->
491− invalidRequest cid st
593+ -- incoming attempt to connect to channel of unknown
594+ -- process - might be dead already
595+ go (incomingAt cid ^= Nothing $ st)
492 NodeIdentifier nid ->
596 NodeIdentifier nid ->
493 if nid == localNodeId node
597 if nid == localNodeId node
494 then go (incomingAt cid ^= Just (src, ToNode) $ st)
598 then go (incomingAt cid ^= Just (src, ToNode) $ st)
495− 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
496 Nothing ->
604 Nothing ->
497 invalidRequest cid st
605 invalidRequest cid st
606+ "message received from an unknown connection"
498 NT.ConnectionClosed cid ->
607 NT.ConnectionClosed cid ->
499 case st ^. incomingAt cid of
608 case st ^. incomingAt cid of
500 Nothing ->
609 Nothing ->
501− invalidRequest cid st
610+ invalidRequest cid st "closed unknown connection"
502 Just (src, _) -> do
611 Just (src, _) -> do
503− trace node (TraceEvDisconnected cid)
612+ trace node (MxDisconnected cid src)
504 go ( (incomingAt cid ^= Nothing)
613 go ( (incomingAt cid ^= Nothing)
505 . (incomingFrom src ^: Set.delete cid)
614 . (incomingFrom src ^: Set.delete cid)
506 $ st
615 $ st
@@ -529,19 +638,20 @@ handleIncomingMessages node = go initConnectionState
529 -- and we just give up
638 -- and we just give up
530 fail "Cloud Haskell fatal error: received unexpected multicast"
639 fail "Cloud Haskell fatal error: received unexpected multicast"
531
640
532− invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
641+ invalidRequest :: NT.ConnectionId -> ConnectionState -> String -> IO ()
533− invalidRequest cid st = do
642+ invalidRequest cid st msg = do
534 -- TODO: We should treat this as a fatal error on the part of the remote
643 -- TODO: We should treat this as a fatal error on the part of the remote
535 -- node. That is, we should report the remote node as having died, and we
644 -- node. That is, we should report the remote node as having died, and we
536 -- should close incoming connections (this requires a Transport layer
645 -- should close incoming connections (this requires a Transport layer
537 -- extension).
646 -- extension).
538− traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
647+ traceEventFmtIO node "" [ TraceStr $ " [network] invalid request"
648+ ++ " (" ++ msg ++ "): "
539− (Trace cid)]
649+ , (Trace cid)
650+ ]
540 go ( incomingAt cid ^= Nothing
651 go ( incomingAt cid ^= Nothing
541 $ st
652 $ st
542 )
653 )
543
654
544− state = localState node
545 endpoint = localEndPoint node
655 endpoint = localEndPoint node
546 ctrlChan = localCtrlChan node
656 ctrlChan = localCtrlChan node
547
657
@@ -550,8 +660,9 @@ handleIncomingMessages node = go initConnectionState
550--------------------------------------------------------------------------------
660--------------------------------------------------------------------------------
551
661
552runNodeController :: LocalNode -> IO ()
662runNodeController :: LocalNode -> IO ()
553−runNodeController =
663+runNodeController node =
554− runReaderT (evalStateT (unNC nodeController) initNCState)
664+ runReaderT (evalStateT (unNC nodeController) initNCState) node
665+ `Exception.catch` \(NodeClosedException _) -> return ()
555
666
556--------------------------------------------------------------------------------
667--------------------------------------------------------------------------------
557-- Internal data types --
668-- Internal data types --
@@ -559,20 +670,26 @@ runNodeController =
559
670
560data NCState = NCState
671data NCState = NCState
561 { -- Mapping from remote processes to linked local processes
672 { -- Mapping from remote processes to linked local processes
562− _links :: !(Map Identifier (Set ProcessId))
673+ _links :: !(BiMultiMap Identifier ProcessId ())
563 -- Mapping from remote processes to monitoring local processes
674 -- Mapping from remote processes to monitoring local processes
564− , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
675+ , _monitors :: !(BiMultiMap Identifier ProcessId MonitorRef)
565 -- Process registry: names and where they live, mapped to the PIDs
676 -- Process registry: names and where they live, mapped to the PIDs
566 , _registeredHere :: !(Map String ProcessId)
677 , _registeredHere :: !(Map String ProcessId)
567 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
678 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
568 }
679 }
569
680
570newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
681newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
682+ deriving ( Applicative
683+ , Functor
684+ , Monad
685+ , MonadIO
686+ , MonadState NCState
571− deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
687+ , MonadReader LocalNode
688+ )
572
689
573initNCState :: NCState
690initNCState :: NCState
574−initNCState = NCState { _links = Map.empty
691+initNCState = NCState { _links = BiMultiMap.empty
575− , _monitors = Map.empty
692+ , _monitors = BiMultiMap.empty
576 , _registeredHere = Map.empty
693 , _registeredHere = Map.empty
577 , _registeredOnNodes = Map.empty
694 , _registeredOnNodes = Map.empty
578 }
695 }
@@ -588,6 +705,33 @@ instance Show ProcessKillException where
588 show (ProcessKillException pid reason) =
705 show (ProcessKillException pid reason) =
589 "killed-by=" ++ show pid ++ ",reason=" ++ reason
706 "killed-by=" ++ show pid ++ ",reason=" ++ reason
590
707
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+
591--------------------------------------------------------------------------------
735--------------------------------------------------------------------------------
592-- Tracing/Debugging --
736-- Tracing/Debugging --
593--------------------------------------------------------------------------------
737--------------------------------------------------------------------------------
@@ -599,8 +743,8 @@ traceNotifyDied node ident reason =
599 -- TODO: sendPortDied notifications
743 -- TODO: sendPortDied notifications
600 liftIO $ withLocalTracer node $ \t ->
744 liftIO $ withLocalTracer node $ \t ->
601 case ident of
745 case ident of
602− (NodeIdentifier nid) -> traceEvent t (TraceEvNodeDied nid reason)
746+ (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
603− (ProcessIdentifier pid) -> traceEvent t (TraceEvDied pid reason)
747+ (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
604 _ -> return ()
748 _ -> return ()
605
749
606traceEventFmtIO :: LocalNode
750traceEventFmtIO :: LocalNode
@@ -610,7 +754,7 @@ traceEventFmtIO :: LocalNode
610traceEventFmtIO node fmt args =
754traceEventFmtIO node fmt args =
611 withLocalTracer node $ \t -> traceLogFmt t fmt args
755 withLocalTracer node $ \t -> traceLogFmt t fmt args
612
756
613−trace :: LocalNode -> TraceEvent -> IO ()
757+trace :: LocalNode -> MxEvent -> IO ()
614trace node ev = withLocalTracer node $ \t -> traceEvent t ev
758trace node ev = withLocalTracer node $ \t -> traceEvent t ev
615
759
616withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
760withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
@@ -630,11 +774,7 @@ nodeController = do
630 -- [Unified: Table 7, rule nc_forward]
774 -- [Unified: Table 7, rule nc_forward]
631 case destNid (ctrlMsgSignal msg) of
775 case destNid (ctrlMsgSignal msg) of
632 Just nid' | nid' /= localNodeId node ->
776 Just nid' | nid' /= localNodeId node ->
633− liftIO $ sendBinary node
777+ ncSendToNode nid' msg
634− (ctrlMsgSender msg)
635− (NodeIdentifier nid')
636− WithImplicitReconnect
637− msg
638 _ ->
778 _ ->
639 return ()
779 return ()
640
780
@@ -657,6 +797,8 @@ nodeController = do
657 ncEffectWhereIs from label
797 ncEffectWhereIs from label
658 NCMsg _ (NamedSend label msg') ->
798 NCMsg _ (NamedSend label msg') ->
659 ncEffectNamedSend label msg'
799 ncEffectNamedSend label msg'
800+ NCMsg _ (UnreliableSend lpid msg') ->
801+ ncEffectLocalSend node (ProcessId (localNodeId node) lpid) msg'
660 NCMsg _ (LocalSend to msg') ->
802 NCMsg _ (LocalSend to msg') ->
661 ncEffectLocalSend node to msg'
803 ncEffectLocalSend node to msg'
662 NCMsg _ (LocalPortSend to msg') ->
804 NCMsg _ (LocalPortSend to msg') ->
@@ -670,8 +812,9 @@ nodeController = do
670 NCMsg _ SigShutdown ->
812 NCMsg _ SigShutdown ->
671 liftIO $ do
813 liftIO $ do
672 NT.closeEndPoint (localEndPoint node)
814 NT.closeEndPoint (localEndPoint node)
673− `Exception.finally` throwIO ThreadKilled
815+ `Exception.finally` throwIO (NodeClosedException $ localNodeId node)
674− -- ThreadKilled seems to make more sense than fail/error here
816+ NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
817+ ncEffectGetNodeStats from nid
675 unexpected ->
818 unexpected ->
676 error $ "nodeController: unexpected message " ++ show unexpected
819 error $ "nodeController: unexpected message " ++ show unexpected
677
820
@@ -689,22 +832,18 @@ ncEffectMonitor from them mRef = do
689 case (shouldLink, isLocal node (ProcessIdentifier from)) of
832 case (shouldLink, isLocal node (ProcessIdentifier from)) of
690 (True, _) -> -- [Unified: first rule]
833 (True, _) -> -- [Unified: first rule]
691 case mRef of
834 case mRef of
692− Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
835+ Just ref -> modify' $ monitors ^: BiMultiMap.insert them from ref
693− Nothing -> modify' $ linksFor them ^: Set.insert from
836+ Nothing -> modify' $ links ^: BiMultiMap.insert them from ()
694 (False, True) -> -- [Unified: second rule]
837 (False, True) -> -- [Unified: second rule]
695 notifyDied from them DiedUnknownId mRef
838 notifyDied from them DiedUnknownId mRef
696 (False, False) -> -- [Unified: third rule]
839 (False, False) -> -- [Unified: third rule]
697 -- TODO: this is the right sender according to the Unified semantics,
840 -- TODO: this is the right sender according to the Unified semantics,
698 -- but perhaps having 'them' as the sender would make more sense
841 -- but perhaps having 'them' as the sender would make more sense
699 -- (see also: notifyDied)
842 -- (see also: notifyDied)
700− liftIO $ sendBinary node
701− (NodeIdentifier $ localNodeId node)
702− (NodeIdentifier $ processNodeId from)
843+ ncSendToNode (processNodeId from) $ NCMsg
703− WithImplicitReconnect
704− NCMsg
705− { ctrlMsgSender = NodeIdentifier (localNodeId node)
844+ { ctrlMsgSender = NodeIdentifier (localNodeId node)
706− , ctrlMsgSignal = Died them DiedUnknownId
845+ , ctrlMsgSignal = Died them DiedUnknownId
707− }
846+ }
708
847
709-- [Unified: Table 11]
848-- [Unified: Table 11]
710ncEffectUnlink :: ProcessId -> Identifier -> NC ()
849ncEffectUnlink :: ProcessId -> Identifier -> NC ()
@@ -718,7 +857,7 @@ ncEffectUnlink from them = do
718 postAsMessage from $ DidUnlinkNode nid
857 postAsMessage from $ DidUnlinkNode nid
719 SendPortIdentifier cid ->
858 SendPortIdentifier cid ->
720 postAsMessage from $ DidUnlinkPort cid
859 postAsMessage from $ DidUnlinkPort cid
721− modify' $ linksFor them ^: Set.delete from
860+ modify' $ links ^: BiMultiMap.delete them from ()
722
861
723-- [Unified: Table 11]
862-- [Unified: Table 11]
724ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
863ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
@@ -726,7 +865,7 @@ ncEffectUnmonitor from ref = do
726 node <- ask
865 node <- ask
727 when (isLocal node (ProcessIdentifier from)) $
866 when (isLocal node (ProcessIdentifier from)) $
728 postAsMessage from $ DidUnmonitor ref
867 postAsMessage from $ DidUnmonitor ref
729− modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
868+ modify' $ monitors ^: BiMultiMap.delete (monitorRefIdent ref) from ref
730
869
731-- [Unified: Table 12]
870-- [Unified: Table 12]
732ncEffectDied :: Identifier -> DiedReason -> NC ()
871ncEffectDied :: Identifier -> DiedReason -> NC ()
@@ -741,7 +880,7 @@ ncEffectDied ident reason = do
741 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
880 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
742
881
743 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
882 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
744− forM_ uss $ \us ->
883+ forM_ uss $ \(us, _) ->
745 when (localOnly <= isLocal node (ProcessIdentifier us)) $
884 when (localOnly <= isLocal node (ProcessIdentifier us)) $
746 notifyDied us them reason Nothing
885 notifyDied us them reason Nothing
747
886
@@ -750,9 +889,35 @@ ncEffectDied ident reason = do
750 when (localOnly <= isLocal node (ProcessIdentifier us)) $
889 when (localOnly <= isLocal node (ProcessIdentifier us)) $
751 notifyDied us them reason (Just ref)
890 notifyDied us them reason (Just ref)
752
891
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+
753− modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
914+ modify' $ (links ^= unaffectedLinks') . (monitors ^= unaffectedMons')
754−
915+
916+ -- we now consider all labels for this identifier unregistered
755− modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
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
756
921
757 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
922 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
758 mapM (\(pid,nidlist) ->
923 mapM (\(pid,nidlist) ->
@@ -760,20 +925,15 @@ ncEffectDied ident reason = do
760 True ->
925 True ->
761 do forM_ nidlist $ \(nid,_) ->
926 do forM_ nidlist $ \(nid,_) ->
762 when (not $ isLocal node (NodeIdentifier nid))
927 when (not $ isLocal node (NodeIdentifier nid))
763− (forwardNameDeath node nid)
928+ (forwardDeath node nid)
764 return Nothing
929 return Nothing
765 False -> return $ Just (pid,nidlist) )
930 False -> return $ Just (pid,nidlist) )
766 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
931 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
767 where
932 where
768− forwardNameDeath node nid =
933+ forwardDeath node nid = ncSendToNode nid
769− liftIO $ sendBinary node
770− (NodeIdentifier $ localNodeId node)
771− (NodeIdentifier $ nid)
772− WithImplicitReconnect
773− NCMsg
774− { ctrlMsgSender = NodeIdentifier (localNodeId node)
934+ NCMsg { ctrlMsgSender = NodeIdentifier (localNodeId node)
775− , ctrlMsgSignal = Died ident reason
935+ , ctrlMsgSignal = Died ident reason
776− }
936+ }
777
937
778-- [Unified: Table 13]
938-- [Unified: Table 13]
779ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
939ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
@@ -781,17 +941,13 @@ ncEffectSpawn pid cProc ref = do
781 mProc <- unClosure cProc
941 mProc <- unClosure cProc
782 -- If the closure does not exist, we spawn a process that throws an exception
942 -- If the closure does not exist, we spawn a process that throws an exception
783 -- This allows the remote node to find out what's happening
943 -- This allows the remote node to find out what's happening
784− -- TODO:
944+ -- TODO:
785 let proc = case mProc of
945 let proc = case mProc of
786 Left err -> fail $ "Error: Could not resolve closure: " ++ err
946 Left err -> fail $ "Error: Could not resolve closure: " ++ err
787 Right p -> p
947 Right p -> p
788 node <- ask
948 node <- ask
789 pid' <- liftIO $ forkProcess node proc
949 pid' <- liftIO $ forkProcess node proc
790− liftIO $ sendMessage node
950+ ncSendToProcess pid $ unsafeCreateUnencodedMessage $ DidSpawn ref pid'
791− (NodeIdentifier (localNodeId node))
792− (ProcessIdentifier pid)
793− WithImplicitReconnect
794− (DidSpawn ref pid')
795
951
796-- Unified semantics does not explicitly describe how to implement 'register',
952-- Unified semantics does not explicitly describe how to implement 'register',
797-- but mentions it's "very similar to nsend" (Table 14)
953-- but mentions it's "very similar to nsend" (Table 14)
@@ -809,17 +965,19 @@ ncEffectRegister from label atnode mPid reregistration = do
809 return $ (isNothing currentVal /= reregistration) &&
965 return $ (isNothing currentVal /= reregistration) &&
810 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
966 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
811 if isLocal node (NodeIdentifier atnode)
967 if isLocal node (NodeIdentifier atnode)
812− then do when (isOk) $
968+ then do when isOk $
813 do modify' $ registeredHereFor label ^= mPid
969 do modify' $ registeredHereFor label ^= mPid
814 updateRemote node currentVal mPid
970 updateRemote node currentVal mPid
815 case mPid of
971 case mPid of
816− (Just p) -> liftIO $ trace node (TraceEvRegistered 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)
817− Nothing -> liftIO $ trace node (TraceEvUnRegistered (fromJust currentVal) label)
977+ Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
818− liftIO $ sendMessage node
978+ newVal <- gets (^. registeredHereFor label)
819− (NodeIdentifier (localNodeId node))
979+ ncSendToProcess from $ unsafeCreateUnencodedMessage $
820− (ProcessIdentifier from)
821− WithImplicitReconnect
822− (RegisterReply label isOk)
980+ RegisterReply label isOk newVal
823 else let operation =
981 else let operation =
824 case reregistration of
982 case reregistration of
825 True -> flip decList
983 True -> flip decList
@@ -849,40 +1007,36 @@ ncEffectRegister from label atnode mPid reregistration = do
849 decList (x:xs) tag = x:decList xs tag
1007 decList (x:xs) tag = x:decList xs tag
850 forward node to reg =
1008 forward node to reg =
851 when (not $ isLocal node (NodeIdentifier to)) $
1009 when (not $ isLocal node (NodeIdentifier to)) $
852− liftIO $ sendBinary node
853− (ProcessIdentifier from)
854− (NodeIdentifier to)
855− WithImplicitReconnect
856− NCMsg
857− { ctrlMsgSender = ProcessIdentifier from
1010+ ncSendToNode to $ NCMsg { ctrlMsgSender = ProcessIdentifier from
858− , ctrlMsgSignal = reg
1011+ , ctrlMsgSignal = reg
859− }
1012+ }
860
1013
861
1014
862-- Unified semantics does not explicitly describe 'whereis'
1015-- Unified semantics does not explicitly describe 'whereis'
863ncEffectWhereIs :: ProcessId -> String -> NC ()
1016ncEffectWhereIs :: ProcessId -> String -> NC ()
864ncEffectWhereIs from label = do
1017ncEffectWhereIs from label = do
865− node <- ask
866 mPid <- gets (^. registeredHereFor label)
1018 mPid <- gets (^. registeredHereFor label)
867− liftIO $ sendMessage node
868− (NodeIdentifier (localNodeId node))
869− (ProcessIdentifier from)
870− WithImplicitReconnect
871− (WhereIsReply label mPid)
1019+ ncSendToProcess from $ unsafeCreateUnencodedMessage $ WhereIsReply label mPid
872
1020
873-- [Unified: Table 14]
1021-- [Unified: Table 14]
874ncEffectNamedSend :: String -> Message -> NC ()
1022ncEffectNamedSend :: String -> Message -> NC ()
875ncEffectNamedSend label msg = do
1023ncEffectNamedSend label msg = do
876 mPid <- gets (^. registeredHereFor label)
1024 mPid <- gets (^. registeredHereFor label)
877 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
1025 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
878− 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
879
1030
880-- [Issue #DP-20]
1031-- [Issue #DP-20]
881ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
1032ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
882−ncEffectLocalSend node to msg =
1033+ncEffectLocalSend = ncEffectLocalSendAndTrace True
1034+
1035+ncEffectLocalSendAndTrace :: Bool -> LocalNode -> ProcessId -> Message -> NC ()
1036+ncEffectLocalSendAndTrace shouldTrace node to msg =
883 liftIO $ withLocalProc node to $ \p -> do
1037 liftIO $ withLocalProc node to $ \p -> do
884 enqueue (processQueue p) msg
1038 enqueue (processQueue p) msg
885− trace node (TraceEvReceived to msg)
1039+ when shouldTrace $ trace node (MxReceived to msg)
886
1040
887-- [Issue #DP-20]
1041-- [Issue #DP-20]
888ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
1042ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
@@ -900,11 +1054,12 @@ ncEffectLocalPortSend from msg = do
900 -- If ch is Nothing, the process has given up the read end of
1054 -- If ch is Nothing, the process has given up the read end of
901 -- the channel and we simply ignore the incoming message - this
1055 -- the channel and we simply ignore the incoming message - this
902 ch <- deRefWeak chan'
1056 ch <- deRefWeak chan'
903− forM_ ch $ \chan -> deliverChan msg chan
1057+ forM_ ch $ \chan -> deliverChan node from msg chan
904− where deliverChan :: forall a . Message -> TQueue a -> IO ()
1058+ where deliverChan :: forall a . LocalNode -> SendPortId -> Message -> TQueue a -> IO ()
905− deliverChan (UnencodedMessage _ raw) chan' =
1059+ deliverChan n p (UnencodedMessage _ raw) chan' = do
906 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
1060 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
1061+ trace n (MxReceivedPort p $ unsafeCreateUnencodedMessage raw)
907− deliverChan (EncodedMessage _ _) _ =
1062+ deliverChan _ _ (EncodedMessage _ _) _ =
908 -- this will not happen unless someone screws with Primitives.hs
1063 -- this will not happen unless someone screws with Primitives.hs
909 error "invalid local channel delivery"
1064 error "invalid local channel delivery"
910
1065
@@ -929,46 +1084,56 @@ ncEffectGetInfo from pid =
929 them = (ProcessIdentifier pid)
1084 them = (ProcessIdentifier pid)
930 in do
1085 in do
931 node <- ask
1086 node <- ask
932− mProc <- liftIO $
1087+ mProc <- liftIO $ withValidLocalState node
933− withMVar (localState node) $ return . (^. localProcessWithId lpid)
1088+ $ return . (^. localProcessWithId lpid)
934 case mProc of
1089 case mProc of
935 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
1090 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
936− from node (ProcessInfoNone DiedUnknownId)
1091+ from (ProcessInfoNone DiedUnknownId)
937− Just _ -> do
1092+ Just proc -> do
938− itsLinks <- gets (^. linksFor them)
1093+ itsLinks <- Set.map fst . BiMultiMap.lookupBy1st them <$>
939− itsMons <- gets (^. monitorsFor them)
1094+ gets (^. links)
1095+ itsMons <- BiMultiMap.lookupBy1st them <$> gets (^. monitors)
940 registered <- gets (^. registeredHere)
1096 registered <- gets (^. registeredHere)
1097+ size <- liftIO $ queueSize $ processQueue $ proc
941
1098
942 let reg = registeredNames registered
1099 let reg = registeredNames registered
943 dispatch (isLocal node (ProcessIdentifier from))
1100 dispatch (isLocal node (ProcessIdentifier from))
944 from
1101 from
945− node
946 ProcessInfo {
1102 ProcessInfo {
947 infoNode = (processNodeId pid)
1103 infoNode = (processNodeId pid)
948 , infoRegisteredNames = reg
1104 , infoRegisteredNames = reg
949− -- we cannot populate this field
950− , infoMessageQueueLength = Nothing
1105+ , infoMessageQueueLength = size
951− , infoMonitors = Set.toList itsMons
1106+ , infoMonitors = Set.toList itsMons
952− , infoLinks = Set.toList itsLinks
1107+ , infoLinks = Set.toList itsLinks
953 }
1108 }
954− where dispatch :: (Serializable a, Show a)
1109+ where dispatch :: (Serializable a)
955 => Bool
1110 => Bool
956 -> ProcessId
1111 -> ProcessId
957− -> LocalNode
958 -> a
1112 -> a
959 -> NC ()
1113 -> NC ()
960− dispatch True dest _ pInfo = postAsMessage dest $ pInfo
1114+ dispatch True dest pInfo = postAsMessage dest $ pInfo
961− dispatch False dest node pInfo = do
1115+ dispatch False dest pInfo =
962− liftIO $ sendMessage node
1116+ ncSendToProcess dest $ unsafeCreateUnencodedMessage pInfo
963− (NodeIdentifier (localNodeId node))
964− (ProcessIdentifier dest)
965− WithImplicitReconnect
966− pInfo
967
1117
968 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1118 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
969 then (k:ks)
1119 then (k:ks)
970 else ks) []
1120 else ks) []
971
1121
1122+ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
1123+ncEffectGetNodeStats from _nid = do
1124+ node <- ask
1125+ ncState <- StateT.get
1126+ nodeState <- liftIO $ withValidLocalState node return
1127+ let stats =
1128+ NodeStats {
1129+ nodeStatsNode = localNodeId node
1130+ , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1131+ , nodeStatsMonitors = BiMultiMap.size $ ncState ^. monitors
1132+ , nodeStatsLinks = BiMultiMap.size $ ncState ^. links
1133+ , nodeStatsProcesses = Map.size (nodeState ^. localProcesses)
1134+ }
1135+ postAsMessage from stats
1136+
972--------------------------------------------------------------------------------
1137--------------------------------------------------------------------------------
973-- Auxiliary --
1138-- Auxiliary --
974--------------------------------------------------------------------------------
1139--------------------------------------------------------------------------------
@@ -995,14 +1160,10 @@ notifyDied dest src reason mRef = do
995 throwException dest $ PortLinkException pid reason
1160 throwException dest $ PortLinkException pid reason
996 (False, _, _) ->
1161 (False, _, _) ->
997 -- The change in sender comes from [Unified: Table 10]
1162 -- The change in sender comes from [Unified: Table 10]
998− liftIO $ sendBinary node
999− (NodeIdentifier $ localNodeId node)
1000− (NodeIdentifier $ processNodeId dest)
1163+ ncSendToNode (processNodeId dest) $ NCMsg
1001− WithImplicitReconnect
1002− NCMsg
1003− { ctrlMsgSender = NodeIdentifier (localNodeId node)
1164+ { ctrlMsgSender = NodeIdentifier (localNodeId node)
1004− , ctrlMsgSignal = Died src reason
1165+ , ctrlMsgSignal = Died src reason
1005− }
1166+ }
1006
1167
1007-- | [Unified: Table 8]
1168-- | [Unified: Table 8]
1008destNid :: ProcessSignal -> Maybe NodeId
1169destNid :: ProcessSignal -> Maybe NodeId
@@ -1014,6 +1175,7 @@ destNid (Spawn _ _) = Nothing
1014destNid (Register _ _ _ _) = Nothing
1175destNid (Register _ _ _ _) = Nothing
1015destNid (WhereIs _) = Nothing
1176destNid (WhereIs _) = Nothing
1016destNid (NamedSend _ _) = Nothing
1177destNid (NamedSend _ _) = Nothing
1178+destNid (UnreliableSend _ _) = Nothing
1017-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1179-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
1018-- then when a local process dies the monitoring/linking machinery will take
1180-- then when a local process dies the monitoring/linking machinery will take
1019-- care of notifying remote nodes
1181-- care of notifying remote nodes
@@ -1021,6 +1183,7 @@ destNid (Died _ _) = Nothing
1021destNid (Kill pid _) = Just $ processNodeId pid
1183destNid (Kill pid _) = Just $ processNodeId pid
1022destNid (Exit pid _) = Just $ processNodeId pid
1184destNid (Exit pid _) = Just $ processNodeId pid
1023destNid (GetInfo pid) = Just $ processNodeId pid
1185destNid (GetInfo pid) = Just $ processNodeId pid
1186+destNid (GetNodeStats nid) = Just nid
1024destNid (LocalSend pid _) = Just $ processNodeId pid
1187destNid (LocalSend pid _) = Just $ processNodeId pid
1025destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1188destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
1026destNid (SigShutdown) = Nothing
1189destNid (SigShutdown) = Nothing
@@ -1039,7 +1202,7 @@ unClosure closure = do
1039isValidLocalIdentifier :: Identifier -> NC Bool
1202isValidLocalIdentifier :: Identifier -> NC Bool
1040isValidLocalIdentifier ident = do
1203isValidLocalIdentifier ident = do
1041 node <- ask
1204 node <- ask
1042− liftIO . withMVar (localState node) $ \nSt ->
1205+ liftIO . withValidLocalState node $ \nSt ->
1043 case ident of
1206 case ident of
1044 NodeIdentifier nid ->
1207 NodeIdentifier nid ->
1045 return $ nid == localNodeId node
1208 return $ nid == localNodeId node
@@ -1070,6 +1233,9 @@ postMessage pid msg = do
1070throwException :: Exception e => ProcessId -> e -> NC ()
1233throwException :: Exception e => ProcessId -> e -> NC ()
1071throwException pid e = do
1234throwException pid e = do
1072 node <- ask
1235 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.
1073 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1239 liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1074
1240
1075withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1241withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
@@ -1077,17 +1243,17 @@ withLocalProc node pid p =
1077 -- By [Unified: table 6, rule missing_process] messages to dead processes
1243 -- By [Unified: table 6, rule missing_process] messages to dead processes
1078 -- can silently be dropped
1244 -- can silently be dropped
1079 let lpid = processLocalId pid in do
1245 let lpid = processLocalId pid in do
1246+ join $ withValidLocalState node $ \vst ->
1080− mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1247+ return $ forM_ (vst ^. localProcessWithId lpid) p
1081− forM_ mProc p
1082
1248
1083--------------------------------------------------------------------------------
1249--------------------------------------------------------------------------------
1084-- Accessors --
1250-- Accessors --
1085--------------------------------------------------------------------------------
1251--------------------------------------------------------------------------------
1086
1252
1087−links :: Accessor NCState (Map Identifier (Set ProcessId))
1253+links :: Accessor NCState (BiMultiMap Identifier ProcessId ())
1088links = accessor _links (\ls st -> st { _links = ls })
1254links = accessor _links (\ls st -> st { _links = ls })
1089
1255
1090−monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1256+monitors :: Accessor NCState (BiMultiMap Identifier ProcessId MonitorRef)
1091monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1257monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1092
1258
1093registeredHere :: Accessor NCState (Map String ProcessId)
1259registeredHere :: Accessor NCState (Map String ProcessId)
@@ -1096,12 +1262,6 @@ registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry }
1096registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1262registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1097registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1263registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1098
1264
1099−linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1100−linksFor ident = links >>> DAC.mapDefault Set.empty ident
1101−
1102−monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1103−monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1104−
1105registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1265registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1106registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1266registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1107
1267
@@ -1128,10 +1288,12 @@ registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1128-- * the notifications for typed channels to that process.
1288-- * the notifications for typed channels to that process.
1129--
1289--
1130-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1290-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1291+splitNotif :: (Ord a, Ord v)
1131−splitNotif :: Identifier
1292+ => Identifier
1132− -> Map Identifier a
1293+ -> BiMultiMap Identifier a v
1133− -> (Map Identifier a, Map Identifier a)
1294+ -> (Map Identifier (Set (a,v)), BiMultiMap Identifier a v)
1134−splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1295+splitNotif ident =
1296+ BiMultiMap.partitionWithKeyBy1st (\k !_v -> ident `impliesDeathOf` k)
1135
1297
1136--------------------------------------------------------------------------------
1298--------------------------------------------------------------------------------
1137-- Auxiliary --
1299-- Auxiliary --
src/Control/Distributed/Process/Serializable.hs+18−8
@@ -1,3 +1,8 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE UndecidableInstances #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+{-# LANGUAGE GADTs #-}
1module Control.Distributed.Process.Serializable
6module Control.Distributed.Process.Serializable
2 ( Serializable
7 ( Serializable
3 , encodeFingerprint
8 , encodeFingerprint
@@ -7,28 +12,33 @@ module Control.Distributed.Process.Serializable
7 , Fingerprint
12 , Fingerprint
8 , showFingerprint
13 , showFingerprint
9 , SerializableDict(SerializableDict)
14 , SerializableDict(SerializableDict)
15+ , TypeableDict(TypeableDict)
10 ) where
16 ) where
11
17
12import Data.Binary (Binary)
18import Data.Binary (Binary)
19+
13−import Data.Typeable (Typeable(..))
20+import Data.Typeable (Typeable, typeRepFingerprint, typeOf)
14−import Data.Typeable.Internal (TypeRep(TypeRep))
21+
15import Numeric (showHex)
22import Numeric (showHex)
16import Control.Exception (throw)
23import Control.Exception (throw)
17import GHC.Fingerprint.Type (Fingerprint(..))
24import GHC.Fingerprint.Type (Fingerprint(..))
18import Data.ByteString (ByteString)
25import Data.ByteString (ByteString)
19import qualified Data.ByteString as BS
26import qualified Data.ByteString as BS
20−import qualified Data.ByteString.Internal as BSI ( unsafeCreate
27+import qualified Data.ByteString.Internal as BSI ( unsafeCreate, toForeignPtr )
21− , inlinePerformIO
22− , toForeignPtr
23− )
24import Foreign.Storable (pokeByteOff, peekByteOff, sizeOf)
28import Foreign.Storable (pokeByteOff, peekByteOff, sizeOf)
25import Foreign.ForeignPtr (withForeignPtr)
29import Foreign.ForeignPtr (withForeignPtr)
30+import System.IO.Unsafe (unsafePerformIO)
26
31
27-- | Reification of 'Serializable' (see "Control.Distributed.Process.Closure")
32-- | Reification of 'Serializable' (see "Control.Distributed.Process.Closure")
28data SerializableDict a where
33data SerializableDict a where
29 SerializableDict :: Serializable a => SerializableDict a
34 SerializableDict :: Serializable a => SerializableDict a
30 deriving (Typeable)
35 deriving (Typeable)
31
36
37+-- | Reification of 'Typeable'.
38+data TypeableDict a where
39+ TypeableDict :: Typeable a => TypeableDict a
40+ deriving (Typeable)
41+
32-- | Objects that can be sent across the network
42-- | Objects that can be sent across the network
33class (Binary a, Typeable a) => Serializable a
43class (Binary a, Typeable a) => Serializable a
34instance (Binary a, Typeable a) => Serializable a
44instance (Binary a, Typeable a) => Serializable a
@@ -45,7 +55,7 @@ decodeFingerprint :: ByteString -> Fingerprint
45decodeFingerprint bs
55decodeFingerprint bs
46 | BS.length bs /= sizeOfFingerprint =
56 | BS.length bs /= sizeOfFingerprint =
47 throw $ userError "decodeFingerprint: Invalid length"
57 throw $ userError "decodeFingerprint: Invalid length"
48− | otherwise = BSI.inlinePerformIO $ do
58+ | otherwise = unsafePerformIO $ do
49 let (fp, offset, _) = BSI.toForeignPtr bs
59 let (fp, offset, _) = BSI.toForeignPtr bs
50 withForeignPtr fp $ \p -> peekByteOff p offset
60 withForeignPtr fp $ \p -> peekByteOff p offset
51
61
@@ -55,7 +65,7 @@ sizeOfFingerprint = sizeOf (undefined :: Fingerprint)
55
65
56-- | The fingerprint of the typeRep of the argument
66-- | The fingerprint of the typeRep of the argument
57fingerprint :: Typeable a => a -> Fingerprint
67fingerprint :: Typeable a => a -> Fingerprint
58−fingerprint a = let TypeRep fp _ _ = typeOf a in fp
68+fingerprint = typeRepFingerprint . typeOf
59
69
60-- | Show fingerprint (for debugging purposes)
70-- | Show fingerprint (for debugging purposes)
61showFingerprint :: Fingerprint -> ShowS
71showFingerprint :: Fingerprint -> ShowS
src/Control/Distributed/Process/UnsafePrimitives.hs+122−36
@@ -24,14 +24,32 @@
24-- by several orders of magnitude.
24-- by several orders of magnitude.
25--
25--
26-- This module provides variants of Cloud Haskell's messaging primitives
26-- This module provides variants of Cloud Haskell's messaging primitives
27−-- ('send', 'sendChan' and 'nsend') which do /not/ force binary serialisation in
27+-- ('send', 'sendChan', 'nsend' and 'wrapMessage') which do /not/ force binary
28−-- the local case, thereby offering superior intra-node messaging performance.
28+-- serialisation in the local case, thereby offering superior intra-node
29−-- The /catch/ is that any evaluation problems lurking within the passed data
29+-- messaging performance. The /catch/ is that any evaluation problems lurking
30−-- structure - e.g., fields set to @undefined@ and so on - will show up in the
30+-- within the passed data structure (e.g., fields set to @undefined@ and so on)
31−-- receiver rather than in the caller (as they would with the /normal/ strategy).
31+-- will show up in the receiver rather than in the caller (as they would with
32+-- the /normal/ strategy).
32--
33--
33-- Use of the functions in this module can potentially change the runtime
34-- Use of the functions in this module can potentially change the runtime
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+--
34−-- behaviour of your application. You have been warned!
52+-- You have been warned!
35--
53--
36-- This module is exported so that you can replace the use of Cloud Haskell's
54-- This module is exported so that you can replace the use of Cloud Haskell's
37-- /safe/ messaging primitives. If you want to use both variants, then you can
55-- /safe/ messaging primitives. If you want to use both variants, then you can
@@ -44,6 +62,9 @@ module Control.Distributed.Process.UnsafePrimitives
44 send
62 send
45 , sendChan
63 , sendChan
46 , nsend
64 , nsend
65+ , nsendRemote
66+ , usend
67+ , wrapMessage
47 ) where
68 ) where
48
69
49import Control.Distributed.Process.Internal.Messaging
70import Control.Distributed.Process.Internal.Messaging
@@ -51,9 +72,15 @@ import Control.Distributed.Process.Internal.Messaging
51 , sendBinary
72 , sendBinary
52 , sendCtrlMsg
73 , sendCtrlMsg
53 )
74 )
54−
75+import Control.Distributed.Process.Management.Internal.Types
76+ ( MxEvent(..)
77+ )
78+import Control.Distributed.Process.Management.Internal.Trace.Types
79+ ( traceEvent
80+ )
55import Control.Distributed.Process.Internal.Types
81import Control.Distributed.Process.Internal.Types
56 ( ProcessId(..)
82 ( ProcessId(..)
83+ , NodeId(..)
57 , LocalNode(..)
84 , LocalNode(..)
58 , LocalProcess(..)
85 , LocalProcess(..)
59 , Process(..)
86 , Process(..)
@@ -62,53 +89,112 @@ import Control.Distributed.Process.Internal.Types
62 , Identifier(..)
89 , Identifier(..)
63 , ImplicitReconnect(..)
90 , ImplicitReconnect(..)
64 , SendPortId(..)
91 , SendPortId(..)
92+ , Message
93+ , createMessage
65 , sendPortProcessId
94 , sendPortProcessId
66 , unsafeCreateUnencodedMessage
95 , unsafeCreateUnencodedMessage
67 )
96 )
68import Control.Distributed.Process.Serializable (Serializable)
97import Control.Distributed.Process.Serializable (Serializable)
69
98
70−import Control.Monad.IO.Class (MonadIO, liftIO)
99+import Control.Monad.IO.Class (liftIO)
71import Control.Monad.Reader (ask)
100import Control.Monad.Reader (ask)
72
101
73-- | Named send to a process in the local registry (asynchronous)
102-- | Named send to a process in the local registry (asynchronous)
74nsend :: Serializable a => String -> a -> Process ()
103nsend :: Serializable a => String -> a -> Process ()
75−nsend label 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')
76− sendCtrlMsg Nothing (NamedSend label (unsafeCreateUnencodedMessage 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))
77
127
78-- | Send a message
128-- | Send a message
79send :: Serializable a => ProcessId -> a -> Process ()
129send :: Serializable a => ProcessId -> a -> Process ()
80send them msg = do
130send them msg = do
81 proc <- ask
131 proc <- ask
82 let node = localNodeId (processNode proc)
132 let node = localNodeId (processNode proc)
83− destNode = (processNodeId them) in do
133+ destNode = (processNodeId them)
84− case destNode == node of
134+ us = (processId proc)
85− True -> unsafeSendLocal them msg
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'
86− False -> liftIO $ sendMessage (processNode proc)
141+ else liftIO $ sendMessage (processNode proc)
87− (ProcessIdentifier (processId proc))
142+ (ProcessIdentifier (processId proc))
88− (ProcessIdentifier them)
143+ (ProcessIdentifier them)
89− NoImplicitReconnect
144+ NoImplicitReconnect
90− msg
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+--
91
176
92-- | Send a message on a typed channel
177-- | Send a message on a typed channel
93sendChan :: Serializable a => SendPort a -> a -> Process ()
178sendChan :: Serializable a => SendPort a -> a -> Process ()
94sendChan (SendPort cid) msg = do
179sendChan (SendPort cid) msg = do
95 proc <- ask
180 proc <- ask
181+ let node = processNode proc
182+ pid = processId proc
96− let node = localNodeId (processNode proc)
183+ us = localNodeId node
97− destNode = processNodeId (sendPortProcessId cid) in do
184+ them = processNodeId (sendPortProcessId cid)
98− case destNode == node of
185+ msg' = wrapMessage msg in do
186+ liftIO $ traceEvent (localEventBus node) (MxSentToPort pid cid msg')
187+ if them == us
99− True -> unsafeSendChanLocal cid msg
188+ then unsafeSendChanLocal cid msg' -- NB: we wrap to P.Message !!!
100− False -> do
101− liftIO $ sendBinary (processNode proc)
189+ else liftIO $ sendBinary node
102− (ProcessIdentifier (processId proc))
190+ (ProcessIdentifier pid)
103− (SendPortIdentifier cid)
191+ (SendPortIdentifier cid)
104− NoImplicitReconnect
192+ NoImplicitReconnect
105− msg
193+ msg
106−
194+ where
107−unsafeSendLocal :: (Serializable a) => ProcessId -> a -> Process ()
108−unsafeSendLocal pid msg =
109− sendCtrlMsg Nothing $ LocalSend pid (unsafeCreateUnencodedMessage msg)
110−
111−unsafeSendChanLocal :: (Serializable a) => SendPortId -> a -> Process ()
195+ unsafeSendChanLocal :: SendPortId -> Message -> Process ()
112−unsafeSendChanLocal spId msg =
113− sendCtrlMsg Nothing $ LocalPortSend spId (unsafeCreateUnencodedMessage msg)
196+ unsafeSendChanLocal p m = sendCtrlMsg Nothing $ LocalPortSend p m
114
197
198+-- | Create an unencoded @Message@ for any @Serializable@ type.
199+wrapMessage :: Serializable a => a -> Message
200+wrapMessage = unsafeCreateUnencodedMessage
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
tests/TestCH.hsdeleted−1339
@@ -1,1339 +0,0 @@
1−module Main where
2−
3−#if ! MIN_VERSION_base(4,6,0)
4−import Prelude hiding (catch)
5−#endif
6−
7−import Data.Binary (Binary(..))
8−import Data.Typeable (Typeable)
9−import Data.Foldable (forM_)
10−import Control.Concurrent (forkIO, threadDelay, myThreadId, throwTo, ThreadId)
11−import Control.Concurrent.MVar
12− ( MVar
13− , newEmptyMVar
14− , putMVar
15− , takeMVar
16− , readMVar
17− )
18−import Control.Monad (replicateM_, replicateM, forever, void)
19−import Control.Exception (SomeException, throwIO)
20−import qualified Control.Exception as Ex (catch)
21−import Control.Applicative ((<$>), (<*>), pure, (<|>))
22−import qualified Network.Transport as NT (Transport, closeEndPoint)
23−import Network.Socket (sClose)
24−import Network.Transport.TCP
25− ( createTransportExposeInternals
26− , TransportInternals(socketBetween)
27− , defaultTCPParameters
28− )
29−import Control.Distributed.Process
30−import Control.Distributed.Process.Internal.Types
31− ( NodeId(nodeAddress)
32− , LocalNode(localEndPoint)
33− , ProcessExitException(..)
34− , nullProcessId
35− )
36−import Control.Distributed.Process.Node
37−import Control.Distributed.Process.Serializable (Serializable)
38−
39−import Test.HUnit (Assertion, assertFailure)
40−import Test.Framework (Test, defaultMain, testGroup)
41−import Test.Framework.Providers.HUnit (testCase)
42−import Control.Rematch hiding (match)
43−import Control.Rematch.Run (Match(..))
44−
45−newtype Ping = Ping ProcessId
46− deriving (Typeable, Binary, Show)
47−
48−newtype Pong = Pong ProcessId
49− deriving (Typeable, Binary, Show)
50−
51−--------------------------------------------------------------------------------
52−-- Supporting definitions --
53−--------------------------------------------------------------------------------
54−
55−expectThat :: a -> Matcher a -> Assertion
56−expectThat a matcher = case res of
57− MatchSuccess -> return ()
58− (MatchFailure msg) -> assertFailure msg
59− where res = runMatch matcher a
60−
61−-- | Like fork, but throw exceptions in the child thread to the parent
62−forkTry :: IO () -> IO ThreadId
63−forkTry p = do
64− tid <- myThreadId
65− forkIO $ Ex.catch p (\e -> throwTo tid (e :: SomeException))
66−
67−-- | The ping server from the paper
68−ping :: Process ()
69−ping = do
70− Pong partner <- expect
71− self <- getSelfPid
72− send partner (Ping self)
73− ping
74−
75−-- | Quick and dirty synchronous version of whereisRemoteAsync
76−whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)
77−whereisRemote nid string = do
78− whereisRemoteAsync nid string
79− WhereIsReply _ mPid <- expect
80− return mPid
81−
82−data Add = Add ProcessId Double Double deriving (Typeable)
83−data Divide = Divide ProcessId Double Double deriving (Typeable)
84−data DivByZero = DivByZero deriving (Typeable)
85−
86−instance Binary Add where
87− put (Add pid x y) = put pid >> put x >> put y
88− get = Add <$> get <*> get <*> get
89−
90−instance Binary Divide where
91− put (Divide pid x y) = put pid >> put x >> put y
92− get = Divide <$> get <*> get <*> get
93−
94−instance Binary DivByZero where
95− put DivByZero = return ()
96− get = return DivByZero
97−
98−-- The math server from the paper
99−math :: Process ()
100−math = do
101− receiveWait
102− [ match (\(Add pid x y) -> send pid (x + y))
103− , matchIf (\(Divide _ _ y) -> y /= 0)
104− (\(Divide pid x y) -> send pid (x / y))
105− , match (\(Divide pid _ _) -> send pid DivByZero)
106− ]
107− math
108−
109−-- | Monitor or link to a remote node
110−monitorOrLink :: Bool -- ^ 'True' for monitor, 'False' for link
111− -> ProcessId -- Process to monitor/link to
112− -> Maybe (MVar ()) -- MVar to signal on once the monitor has been set up
113− -> Process (Maybe MonitorRef)
114−monitorOrLink mOrL pid mSignal = do
115− result <- if mOrL then Just <$> monitor pid
116− else link pid >> return Nothing
117− -- Monitor is asynchronous, which usually does not matter but if we want a
118− -- *specific* signal then it does. Therefore we wait an arbitrary delay and
119− -- hope that this means the monitor has been set up
120− forM_ mSignal $ \signal -> liftIO . forkIO $ threadDelay 100000 >> putMVar signal ()
121− return result
122−
123−monitorTestProcess :: ProcessId -- Process to monitor/link to
124− -> Bool -- 'True' for monitor, 'False' for link
125− -> Bool -- Should we unmonitor?
126− -> DiedReason -- Expected cause of death
127− -> Maybe (MVar ()) -- Signal for 'monitor set up'
128− -> MVar () -- Signal for successful termination
129− -> Process ()
130−monitorTestProcess theirAddr mOrL un reason monitorSetup done =
131− catch (do mRef <- monitorOrLink mOrL theirAddr monitorSetup
132− case (un, mRef) of
133− (True, Nothing) -> do
134− unlink theirAddr
135− liftIO $ putMVar done ()
136− (True, Just ref) -> do
137− unmonitor ref
138− liftIO $ putMVar done ()
139− (False, ref) -> do
140− ProcessMonitorNotification ref' pid reason' <- expect
141− True <- return $ Just ref' == ref && pid == theirAddr && mOrL && reason == reason'
142− liftIO $ putMVar done ()
143− )
144− (\(ProcessLinkException pid reason') -> do
145− True <- return $ pid == theirAddr && not mOrL && not un && reason == reason'
146− liftIO $ putMVar done ()
147− )
148−
149−--------------------------------------------------------------------------------
150−-- The tests proper --
151−--------------------------------------------------------------------------------
152−
153−-- | Basic ping test
154−testPing :: NT.Transport -> Assertion
155−testPing transport = do
156− serverAddr <- newEmptyMVar
157− clientDone <- newEmptyMVar
158−
159− -- Server
160− forkIO $ do
161− localNode <- newLocalNode transport initRemoteTable
162− addr <- forkProcess localNode ping
163− putMVar serverAddr addr
164−
165− -- Client
166− forkIO $ do
167− localNode <- newLocalNode transport initRemoteTable
168− pingServer <- readMVar serverAddr
169−
170− let numPings = 10000
171−
172− runProcess localNode $ do
173− pid <- getSelfPid
174− replicateM_ numPings $ do
175− send pingServer (Pong pid)
176− Ping _ <- expect
177− return ()
178−
179− putMVar clientDone ()
180−
181− takeMVar clientDone
182−
183−-- | Monitor a process on an unreachable node
184−testMonitorUnreachable :: NT.Transport -> Bool -> Bool -> Assertion
185−testMonitorUnreachable transport mOrL un = do
186− deadProcess <- newEmptyMVar
187− done <- newEmptyMVar
188−
189− forkIO $ do
190− localNode <- newLocalNode transport initRemoteTable
191− addr <- forkProcess localNode . liftIO $ threadDelay 1000000
192− closeLocalNode localNode
193− putMVar deadProcess addr
194−
195− forkIO $ do
196− localNode <- newLocalNode transport initRemoteTable
197− theirAddr <- readMVar deadProcess
198− runProcess localNode $
199− monitorTestProcess theirAddr mOrL un DiedDisconnect Nothing done
200−
201− takeMVar done
202−
203−-- | Monitor a process which terminates normally
204−testMonitorNormalTermination :: NT.Transport -> Bool -> Bool -> Assertion
205−testMonitorNormalTermination transport mOrL un = do
206− monitorSetup <- newEmptyMVar
207− monitoredProcess <- newEmptyMVar
208− done <- newEmptyMVar
209−
210− forkIO $ do
211− localNode <- newLocalNode transport initRemoteTable
212− addr <- forkProcess localNode $
213− liftIO $ readMVar monitorSetup
214− putMVar monitoredProcess addr
215−
216− forkIO $ do
217− localNode <- newLocalNode transport initRemoteTable
218− theirAddr <- readMVar monitoredProcess
219− runProcess localNode $
220− monitorTestProcess theirAddr mOrL un DiedNormal (Just monitorSetup) done
221−
222− takeMVar done
223−
224−-- | Monitor a process which terminates abnormally
225−testMonitorAbnormalTermination :: NT.Transport -> Bool -> Bool -> Assertion
226−testMonitorAbnormalTermination transport mOrL un = do
227− monitorSetup <- newEmptyMVar
228− monitoredProcess <- newEmptyMVar
229− done <- newEmptyMVar
230−
231− let err = userError "Abnormal termination"
232−
233− forkIO $ do
234− localNode <- newLocalNode transport initRemoteTable
235− addr <- forkProcess localNode . liftIO $ do
236− readMVar monitorSetup
237− throwIO err
238− putMVar monitoredProcess addr
239−
240− forkIO $ do
241− localNode <- newLocalNode transport initRemoteTable
242− theirAddr <- readMVar monitoredProcess
243− runProcess localNode $
244− monitorTestProcess theirAddr mOrL un (DiedException (show err)) (Just monitorSetup) done
245−
246− takeMVar done
247−
248−-- | Monitor a local process that is already dead
249−testMonitorLocalDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion
250−testMonitorLocalDeadProcess transport mOrL un = do
251− processDead <- newEmptyMVar
252− processAddr <- newEmptyMVar
253− localNode <- newLocalNode transport initRemoteTable
254− done <- newEmptyMVar
255−
256− forkIO $ do
257− addr <- forkProcess localNode . liftIO $ putMVar processDead ()
258− putMVar processAddr addr
259−
260− forkIO $ do
261− theirAddr <- readMVar processAddr
262− readMVar processDead
263− runProcess localNode $ do
264− monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
265−
266− takeMVar done
267−
268−-- | Monitor a remote process that is already dead
269−testMonitorRemoteDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion
270−testMonitorRemoteDeadProcess transport mOrL un = do
271− processDead <- newEmptyMVar
272− processAddr <- newEmptyMVar
273− done <- newEmptyMVar
274−
275− forkIO $ do
276− localNode <- newLocalNode transport initRemoteTable
277− addr <- forkProcess localNode . liftIO $ putMVar processDead ()
278− putMVar processAddr addr
279−
280− forkIO $ do
281− localNode <- newLocalNode transport initRemoteTable
282− theirAddr <- readMVar processAddr
283− readMVar processDead
284− runProcess localNode $ do
285− monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
286−
287− takeMVar done
288−
289−-- | Monitor a process that becomes disconnected
290−testMonitorDisconnect :: NT.Transport -> Bool -> Bool -> Assertion
291−testMonitorDisconnect transport mOrL un = do
292− processAddr <- newEmptyMVar
293− monitorSetup <- newEmptyMVar
294− done <- newEmptyMVar
295−
296− forkIO $ do
297− localNode <- newLocalNode transport initRemoteTable
298− addr <- forkProcess localNode . liftIO $ threadDelay 1000000
299− putMVar processAddr addr
300− readMVar monitorSetup
301− NT.closeEndPoint (localEndPoint localNode)
302−
303− forkIO $ do
304− localNode <- newLocalNode transport initRemoteTable
305− theirAddr <- readMVar processAddr
306− runProcess localNode $ do
307− monitorTestProcess theirAddr mOrL un DiedDisconnect (Just monitorSetup) done
308−
309− takeMVar done
310−
311−-- | Test the math server (i.e., receiveWait)
312−testMath :: NT.Transport -> Assertion
313−testMath transport = do
314− serverAddr <- newEmptyMVar
315− clientDone <- newEmptyMVar
316−
317− -- Server
318− forkIO $ do
319− localNode <- newLocalNode transport initRemoteTable
320− addr <- forkProcess localNode math
321− putMVar serverAddr addr
322−
323− -- Client
324− forkIO $ do
325− localNode <- newLocalNode transport initRemoteTable
326− mathServer <- readMVar serverAddr
327−
328− runProcess localNode $ do
329− pid <- getSelfPid
330− send mathServer (Add pid 1 2)
331− 3 <- expect :: Process Double
332− send mathServer (Divide pid 8 2)
333− 4 <- expect :: Process Double
334− send mathServer (Divide pid 8 0)
335− DivByZero <- expect
336− liftIO $ putMVar clientDone ()
337−
338− takeMVar clientDone
339−
340−-- | Send first message (i.e. connect) to an already terminated process
341−-- (without monitoring); then send another message to a second process on
342−-- the same remote node (we're checking that the remote node did not die)
343−testSendToTerminated :: NT.Transport -> Assertion
344−testSendToTerminated transport = do
345− serverAddr1 <- newEmptyMVar
346− serverAddr2 <- newEmptyMVar
347− clientDone <- newEmptyMVar
348−
349− forkIO $ do
350− terminated <- newEmptyMVar
351− localNode <- newLocalNode transport initRemoteTable
352− addr1 <- forkProcess localNode $ liftIO $ putMVar terminated ()
353− addr2 <- forkProcess localNode $ ping
354− readMVar terminated
355− putMVar serverAddr1 addr1
356− putMVar serverAddr2 addr2
357−
358− forkIO $ do
359− localNode <- newLocalNode transport initRemoteTable
360− server1 <- readMVar serverAddr1
361− server2 <- readMVar serverAddr2
362− runProcess localNode $ do
363− pid <- getSelfPid
364− send server1 "Hi"
365− send server2 (Pong pid)
366− Ping pid' <- expect
367− True <- return $ pid' == server2
368− liftIO $ putMVar clientDone ()
369−
370− takeMVar clientDone
371−
372−-- | Test (non-zero) timeout
373−testTimeout :: NT.Transport -> Assertion
374−testTimeout transport = do
375− localNode <- newLocalNode transport initRemoteTable
376− done <- newEmptyMVar
377−
378− runProcess localNode $ do
379− Nothing <- receiveTimeout 1000000 [match (\(Add _ _ _) -> return ())]
380− liftIO $ putMVar done ()
381−
382− takeMVar done
383−
384−-- | Test zero timeout
385−testTimeout0 :: NT.Transport -> Assertion
386−testTimeout0 transport = do
387− serverAddr <- newEmptyMVar
388− clientDone <- newEmptyMVar
389− messagesSent <- newEmptyMVar
390−
391− forkIO $ do
392− localNode <- newLocalNode transport initRemoteTable
393− addr <- forkProcess localNode $ do
394− liftIO $ readMVar messagesSent >> threadDelay 1000000
395− -- Variation on the venerable ping server which uses a zero timeout
396− -- Since we wait for all messages to be sent before doing this receive,
397− -- we should nevertheless find the right message immediately
398− Just partner <- receiveTimeout 0 [match (\(Pong partner) -> return partner)]
399− self <- getSelfPid
400− send partner (Ping self)
401− putMVar serverAddr addr
402−
403− forkIO $ do
404− localNode <- newLocalNode transport initRemoteTable
405− server <- readMVar serverAddr
406− runProcess localNode $ do
407− pid <- getSelfPid
408− -- Send a bunch of messages. A large number of messages that the server
409− -- is not interested in, and then a single message that it wants
410− replicateM_ 10000 $ send server "Irrelevant message"
411− send server (Pong pid)
412− liftIO $ putMVar messagesSent ()
413− Ping _ <- expect
414− liftIO $ putMVar clientDone ()
415−
416− takeMVar clientDone
417−
418−-- | Test typed channels
419−testTypedChannels :: NT.Transport -> Assertion
420−testTypedChannels transport = do
421− serverChannel <- newEmptyMVar :: IO (MVar (SendPort (SendPort Bool, Int)))
422− clientDone <- newEmptyMVar
423−
424− forkIO $ do
425− localNode <- newLocalNode transport initRemoteTable
426− forkProcess localNode $ do
427− (serverSendPort, rport) <- newChan
428− liftIO $ putMVar serverChannel serverSendPort
429− (clientSendPort, i) <- receiveChan rport
430− sendChan clientSendPort (even i)
431− return ()
432−
433− forkIO $ do
434− localNode <- newLocalNode transport initRemoteTable
435− serverSendPort <- readMVar serverChannel
436− runProcess localNode $ do
437− (clientSendPort, rport) <- newChan
438− sendChan serverSendPort (clientSendPort, 5)
439− False <- receiveChan rport
440− liftIO $ putMVar clientDone ()
441−
442− takeMVar clientDone
443−
444−-- | Test merging receive ports
445−testMergeChannels :: NT.Transport -> Assertion
446−testMergeChannels transport = do
447− localNode <- newLocalNode transport initRemoteTable
448− testFlat localNode True "aaabbbccc"
449− testFlat localNode False "abcabcabc"
450− testNested localNode True True "aaabbbcccdddeeefffggghhhiii"
451− testNested localNode True False "adgadgadgbehbehbehcficficfi"
452− testNested localNode False True "abcabcabcdefdefdefghighighi"
453− testNested localNode False False "adgbehcfiadgbehcfiadgbehcfi"
454− testBlocked localNode True
455− testBlocked localNode False
456− where
457− -- Single layer of merging
458− testFlat :: LocalNode -> Bool -> String -> IO ()
459− testFlat localNode biased expected = do
460− done <- newEmptyMVar
461− forkProcess localNode $ do
462− rs <- mapM charChannel "abc"
463− m <- mergePorts biased rs
464− xs <- replicateM 9 $ receiveChan m
465− True <- return $ xs == expected
466− liftIO $ putMVar done ()
467− takeMVar done
468−
469− -- Two layers of merging
470− testNested :: LocalNode -> Bool -> Bool -> String -> IO ()
471− testNested localNode biasedInner biasedOuter expected = do
472− done <- newEmptyMVar
473− forkProcess localNode $ do
474− rss <- mapM (mapM charChannel) ["abc", "def", "ghi"]
475− ms <- mapM (mergePorts biasedInner) rss
476− m <- mergePorts biasedOuter ms
477− xs <- replicateM (9 * 3) $ receiveChan m
478− True <- return $ xs == expected
479− liftIO $ putMVar done ()
480− takeMVar done
481−
482− -- Test that if no messages are (immediately) available, the scheduler makes no difference
483− testBlocked :: LocalNode -> Bool -> IO ()
484− testBlocked localNode biased = do
485− vs <- replicateM 3 newEmptyMVar
486− done <- newEmptyMVar
487−
488− forkProcess localNode $ do
489− [sa, sb, sc] <- liftIO $ mapM readMVar vs
490− mapM_ ((>> liftIO (threadDelay 10000)) . uncurry sendChan)
491− [ -- a, b, c
492− (sa, 'a')
493− , (sb, 'b')
494− , (sc, 'c')
495− -- a, c, b
496− , (sa, 'a')
497− , (sc, 'c')
498− , (sb, 'b')
499− -- b, a, c
500− , (sb, 'b')
501− , (sa, 'a')
502− , (sc, 'c')
503− -- b, c, a
504− , (sb, 'b')
505− , (sc, 'c')
506− , (sa, 'a')
507− -- c, a, b
508− , (sc, 'c')
509− , (sa, 'a')
510− , (sb, 'b')
511− -- c, b, a
512− , (sc, 'c')
513− , (sb, 'b')
514− , (sa, 'a')
515− ]
516−
517− forkProcess localNode $ do
518− (ss, rs) <- unzip <$> replicateM 3 newChan
519− liftIO $ mapM_ (uncurry putMVar) $ zip vs ss
520− m <- mergePorts biased rs
521− xs <- replicateM (6 * 3) $ receiveChan m
522− True <- return $ xs == "abcacbbacbcacabcba"
523− liftIO $ putMVar done ()
524−
525− takeMVar done
526−
527− mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)
528− mergePorts True = mergePortsBiased
529− mergePorts False = mergePortsRR
530−
531− charChannel :: Char -> Process (ReceivePort Char)
532− charChannel c = do
533− (sport, rport) <- newChan
534− replicateM_ 3 $ sendChan sport c
535− liftIO $ threadDelay 10000 -- Make sure messages have been sent
536− return rport
537−
538−testTerminate :: NT.Transport -> Assertion
539−testTerminate transport = do
540− localNode <- newLocalNode transport initRemoteTable
541− done <- newEmptyMVar
542−
543− pid <- forkProcess localNode $ do
544− liftIO $ threadDelay 100000
545− terminate
546−
547− runProcess localNode $ do
548− ref <- monitor pid
549− ProcessMonitorNotification ref' pid' (DiedException ex) <- expect
550− True <- return $ ref == ref' && pid == pid' && ex == show ProcessTerminationException
551− liftIO $ putMVar done ()
552−
553− takeMVar done
554−
555−testMonitorNode :: NT.Transport -> Assertion
556−testMonitorNode transport = do
557− [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
558− done <- newEmptyMVar
559−
560− closeLocalNode node1
561−
562− runProcess node2 $ do
563− ref <- monitorNode (localNodeId node1)
564− NodeMonitorNotification ref' nid DiedDisconnect <- expect
565− True <- return $ ref == ref' && nid == localNodeId node1
566− liftIO $ putMVar done ()
567−
568− takeMVar done
569−
570−testMonitorLiveNode :: NT.Transport -> Assertion
571−testMonitorLiveNode transport = do
572− [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
573− ready <- newEmptyMVar
574− done <- newEmptyMVar
575−
576− forkProcess node2 $ do
577− ref <- monitorNode (localNodeId node1)
578− liftIO $ putMVar ready ()
579− NodeMonitorNotification ref' nid _ <- expect
580− True <- return $ ref == ref' && nid == localNodeId node1
581− liftIO $ putMVar done ()
582−
583− takeMVar ready
584− closeLocalNode node1
585−
586− takeMVar done
587−
588−testMonitorChannel :: NT.Transport -> Assertion
589−testMonitorChannel transport = do
590− [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
591− gotNotification <- newEmptyMVar
592−
593− pid <- forkProcess node1 $ do
594− sport <- expect :: Process (SendPort ())
595− ref <- monitorPort sport
596− PortMonitorNotification ref' port' reason <- expect
597− -- reason might be DiedUnknownId if the receive port is GCed before the
598− -- monitor is established (TODO: not sure that this is reasonable)
599− return $ ref' == ref && port' == sendPortId sport && (reason == DiedNormal || reason == DiedUnknownId)
600− liftIO $ putMVar gotNotification ()
601−
602− runProcess node2 $ do
603− (sport, _) <- newChan :: Process (SendPort (), ReceivePort ())
604− send pid sport
605− liftIO $ threadDelay 100000
606−
607− takeMVar gotNotification
608−
609−testRegistry :: NT.Transport -> Assertion
610−testRegistry transport = do
611− node <- newLocalNode transport initRemoteTable
612− done <- newEmptyMVar
613−
614− pingServer <- forkProcess node ping
615−
616− runProcess node $ do
617− register "ping" pingServer
618− Just pid <- whereis "ping"
619− True <- return $ pingServer == pid
620− us <- getSelfPid
621− nsend "ping" (Pong us)
622− Ping pid' <- expect
623− True <- return $ pingServer == pid'
624− liftIO $ putMVar done ()
625−
626− takeMVar done
627−
628−testRemoteRegistry :: NT.Transport -> Assertion
629−testRemoteRegistry transport = do
630− node1 <- newLocalNode transport initRemoteTable
631− node2 <- newLocalNode transport initRemoteTable
632− done <- newEmptyMVar
633−
634− pingServer <- forkProcess node1 ping
635−
636− runProcess node2 $ do
637− let nid1 = localNodeId node1
638− registerRemoteAsync nid1 "ping" pingServer
639− receiveWait [
640− matchIf (\(RegisterReply label' _) -> "ping" == label')
641− (\(RegisterReply _ _) -> return ()) ]
642−
643− Just pid <- whereisRemote nid1 "ping"
644− True <- return $ pingServer == pid
645− us <- getSelfPid
646− nsendRemote nid1 "ping" (Pong us)
647− Ping pid' <- expect
648− True <- return $ pingServer == pid'
649− liftIO $ putMVar done ()
650−
651− takeMVar done
652−
653−testSpawnLocal :: NT.Transport -> Assertion
654−testSpawnLocal transport = do
655− node <- newLocalNode transport initRemoteTable
656− done <- newEmptyMVar
657−
658− runProcess node $ do
659− us <- getSelfPid
660−
661− pid <- spawnLocal $ do
662− sport <- expect
663− sendChan sport (1234 :: Int)
664−
665− sport <- spawnChannelLocal $ \rport -> do
666− (1234 :: Int) <- receiveChan rport
667− send us ()
668−
669− send pid sport
670− () <- expect
671− liftIO $ putMVar done ()
672−
673− takeMVar done
674−
675−testReconnect :: NT.Transport -> TransportInternals -> Assertion
676−testReconnect transport transportInternals = do
677− [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
678− let nid1 = localNodeId node1
679− nid2 = localNodeId node2
680− processA <- newEmptyMVar
681− [sendTestOk, registerTestOk] <- replicateM 2 newEmptyMVar
682−
683− forkProcess node1 $ do
684− us <- getSelfPid
685− liftIO $ putMVar processA us
686− msg1 <- expect
687− msg2 <- expect
688− True <- return $ msg1 == "message 1" && msg2 == "message 3"
689− liftIO $ putMVar sendTestOk ()
690−
691− forkProcess node2 $ do
692− {-
693− - Make sure there is no implicit reconnect on normal message sending
694− -}
695−
696− them <- liftIO $ readMVar processA
697− send them "message 1" >> liftIO (threadDelay 100000)
698−
699− -- Simulate network failure
700− liftIO $ do
701− sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)
702− sClose sock
703− threadDelay 10000
704−
705− -- Should not arrive
706− send them "message 2"
707−
708− -- Should arrive
709− reconnect them
710− send them "message 3"
711−
712− liftIO $ takeMVar sendTestOk
713−
714− {-
715− - Test that there *is* implicit reconnect on node controller messages
716− -}
717−
718− us <- getSelfPid
719− registerRemoteAsync nid1 "a" us -- registerRemote is asynchronous
720− receiveWait [
721− matchIf (\(RegisterReply label' _) -> "a" == label')
722− (\(RegisterReply _ _) -> return ()) ]
723−
724− Just _ <- whereisRemote nid1 "a"
725−
726−
727− -- Simulate network failure
728− liftIO $ do
729− sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)
730− sClose sock
731− threadDelay 10000
732−
733− -- This will happen due to implicit reconnect
734− registerRemoteAsync nid1 "b" us
735− receiveWait [
736− matchIf (\(RegisterReply label' _) -> "b" == label')
737− (\(RegisterReply _ _) -> return ()) ]
738−
739− -- Should happen
740− registerRemoteAsync nid1 "c" us
741− receiveWait [
742− matchIf (\(RegisterReply label' _) -> "c" == label')
743− (\(RegisterReply _ _) -> return ()) ]
744−
745− -- Check
746− Nothing <- whereisRemote nid1 "a" -- this will fail because the name is removed when the node is disconnected
747− Just _ <- whereisRemote nid1 "b" -- this will suceed because the value is set after thereconnect
748− Just _ <- whereisRemote nid1 "c"
749−
750− liftIO $ putMVar registerTestOk ()
751−
752− takeMVar registerTestOk
753−
754−-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
755−-- in between
756−testMatchAny :: NT.Transport -> Assertion
757−testMatchAny transport = do
758− proxyAddr <- newEmptyMVar
759− clientDone <- newEmptyMVar
760−
761− -- Math server
762− forkIO $ do
763− localNode <- newLocalNode transport initRemoteTable
764− mathServer <- forkProcess localNode math
765− proxyServer <- forkProcess localNode $ forever $ do
766− msg <- receiveWait [ matchAny return ]
767− forward msg mathServer
768− putMVar proxyAddr proxyServer
769−
770− -- Client
771− forkIO $ do
772− localNode <- newLocalNode transport initRemoteTable
773− mathServer <- readMVar proxyAddr
774−
775− runProcess localNode $ do
776− pid <- getSelfPid
777− send mathServer (Add pid 1 2)
778− 3 <- expect :: Process Double
779− send mathServer (Divide pid 8 2)
780− 4 <- expect :: Process Double
781− send mathServer (Divide pid 8 0)
782− DivByZero <- expect
783− liftIO $ putMVar clientDone ()
784−
785− takeMVar clientDone
786−
787−-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
788−-- in between, however we block 'Divide' requests ....
789−testMatchAnyHandle :: NT.Transport -> Assertion
790−testMatchAnyHandle transport = do
791− proxyAddr <- newEmptyMVar
792− clientDone <- newEmptyMVar
793−
794− -- Math server
795− forkIO $ do
796− localNode <- newLocalNode transport initRemoteTable
797− mathServer <- forkProcess localNode math
798− proxyServer <- forkProcess localNode $ forever $ do
799− receiveWait [
800− matchAny (maybeForward mathServer)
801− ]
802− putMVar proxyAddr proxyServer
803−
804− -- Client
805− forkIO $ do
806− localNode <- newLocalNode transport initRemoteTable
807− mathServer <- readMVar proxyAddr
808−
809− runProcess localNode $ do
810− pid <- getSelfPid
811− send mathServer (Add pid 1 2)
812− 3 <- expect :: Process Double
813− send mathServer (Divide pid 8 2)
814− Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
815− liftIO $ putMVar clientDone ()
816−
817− takeMVar clientDone
818− where maybeForward :: ProcessId -> Message -> Process (Maybe ())
819− maybeForward s msg =
820− handleMessage msg (\m@(Add _ _ _) -> send s m)
821−
822−testMatchAnyNoHandle :: NT.Transport -> Assertion
823−testMatchAnyNoHandle transport = do
824− addr <- newEmptyMVar
825− clientDone <- newEmptyMVar
826− serverDone <- newEmptyMVar
827−
828− -- Math server
829− forkIO $ do
830− localNode <- newLocalNode transport initRemoteTable
831− server <- forkProcess localNode $ forever $ do
832− receiveWait [
833− matchAnyIf
834− -- the condition has type `Add -> Bool`
835− (\(Add _ _ _) -> True)
836− -- the match `AbstractMessage -> Process ()` will succeed!
837− (\m -> do
838− -- `String -> Process ()` does *not* match the input types however
839− r <- (handleMessage m (\(_ :: String) -> die "NONSENSE" ))
840− case r of
841− Nothing -> return ()
842− Just _ -> die "NONSENSE")
843− ]
844− -- we *must* have removed the message from our mailbox though!!!
845− Nothing <- receiveTimeout 100000 [ match (\(Add _ _ _) -> return ()) ]
846− liftIO $ putMVar serverDone ()
847− putMVar addr server
848−
849− -- Client
850− forkIO $ do
851− localNode <- newLocalNode transport initRemoteTable
852− server <- readMVar addr
853−
854− runProcess localNode $ do
855− pid <- getSelfPid
856− send server (Add pid 1 2)
857− -- we only care about the client having sent a message, so we're done
858− liftIO $ putMVar clientDone ()
859−
860− takeMVar clientDone
861− takeMVar serverDone
862−
863−-- | Test 'matchAnyIf'. We provide an /echo/ server, but it ignores requests
864−-- unless the text body @/= "bar"@ - this case should time out rather than
865−-- removing the message from the process mailbox.
866−testMatchAnyIf :: NT.Transport -> Assertion
867−testMatchAnyIf transport = do
868− echoAddr <- newEmptyMVar
869− clientDone <- newEmptyMVar
870−
871− -- echo server
872− forkIO $ do
873− localNode <- newLocalNode transport initRemoteTable
874− echoServer <- forkProcess localNode $ forever $ do
875− receiveWait [
876− matchAnyIf (\(_ :: ProcessId, (s :: String)) -> s /= "bar")
877− tryHandleMessage
878− ]
879− putMVar echoAddr echoServer
880−
881− -- Client
882− forkIO $ do
883− localNode <- newLocalNode transport initRemoteTable
884− server <- readMVar echoAddr
885−
886− runProcess localNode $ do
887− pid <- getSelfPid
888− send server (pid, "foo")
889− "foo" <- expect
890− send server (pid, "baz")
891− "baz" <- expect
892− send server (pid, "bar")
893− Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
894− liftIO $ putMVar clientDone ()
895−
896− takeMVar clientDone
897− where tryHandleMessage :: Message -> Process (Maybe ())
898− tryHandleMessage msg =
899− handleMessage msg (\(pid :: ProcessId, (m :: String))
900− -> do { send pid m; return () })
901−
902−testMatchMessageWithUnwrap :: NT.Transport -> Assertion
903−testMatchMessageWithUnwrap transport = do
904− echoAddr <- newEmptyMVar
905− clientDone <- newEmptyMVar
906−
907− -- echo server
908− forkIO $ do
909− localNode <- newLocalNode transport initRemoteTable
910− echoServer <- forkProcess localNode $ forever $ do
911− msg <- receiveWait [
912− matchMessage (\(m :: Message) -> do
913− return m)
914− ]
915− unwrapped <- unwrapMessage msg :: Process (Maybe (ProcessId, Message))
916− case unwrapped of
917− (Just (p, msg')) -> forward msg' p
918− Nothing -> die "unable to unwrap the message"
919− putMVar echoAddr echoServer
920−
921− -- Client
922− forkIO $ do
923− localNode <- newLocalNode transport initRemoteTable
924− server <- readMVar echoAddr
925−
926− runProcess localNode $ do
927− pid <- getSelfPid
928− send server (pid, wrapMessage ("foo" :: String))
929− "foo" <- expect
930− send server (pid, wrapMessage ("baz" :: String))
931− "baz" <- expect
932− liftIO $ putMVar clientDone ()
933−
934− takeMVar clientDone
935−
936−-- Test 'receiveChanTimeout'
937−testReceiveChanTimeout :: NT.Transport -> Assertion
938−testReceiveChanTimeout transport = do
939− done <- newEmptyMVar
940− sendPort <- newEmptyMVar
941−
942− forkTry $ do
943− localNode <- newLocalNode transport initRemoteTable
944− runProcess localNode $ do
945− -- Create a typed channel
946− (sp, rp) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
947− liftIO $ putMVar sendPort sp
948−
949− -- Wait for a message with a delay. No message arrives, we should get Nothing after 1 second
950− Nothing <- receiveChanTimeout 1000000 rp
951−
952− -- Wait for a message with a delay again. Now a message arrives after 0.5 seconds
953− Just True <- receiveChanTimeout 1000000 rp
954−
955− -- Wait for a message with zero timeout: non-blocking check. No message is available, we get Nothing
956− Nothing <- receiveChanTimeout 0 rp
957−
958− -- Again, but now there is a message available
959− liftIO $ threadDelay 1000000
960− Just False <- receiveChanTimeout 0 rp
961−
962− liftIO $ putMVar done ()
963−
964− forkTry $ do
965− localNode <- newLocalNode transport initRemoteTable
966− runProcess localNode $ do
967− sp <- liftIO $ readMVar sendPort
968−
969− liftIO $ threadDelay 1500000
970− sendChan sp True
971−
972− liftIO $ threadDelay 500000
973− sendChan sp False
974−
975− takeMVar done
976−
977−-- | Test Functor, Applicative, Alternative and Monad instances for ReceiveChan
978−testReceiveChanFeatures :: NT.Transport -> Assertion
979−testReceiveChanFeatures transport = do
980− done <- newEmptyMVar
981−
982− forkTry $ do
983− localNode <- newLocalNode transport initRemoteTable
984− runProcess localNode $ do
985− (spInt, rpInt) <- newChan :: Process (SendPort Int, ReceivePort Int)
986− (spBool, rpBool) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
987−
988− -- Test Functor instance
989−
990− sendChan spInt 2
991− sendChan spBool False
992−
993− rp1 <- mergePortsBiased [even <$> rpInt, rpBool]
994−
995− True <- receiveChan rp1
996− False <- receiveChan rp1
997−
998− -- Test Applicative instance
999−
1000− sendChan spInt 3
1001− sendChan spInt 4
1002−
1003− let rp2 = pure (+) <*> rpInt <*> rpInt
1004−
1005− 7 <- receiveChan rp2
1006−
1007− -- Test Alternative instance
1008−
1009− sendChan spInt 3
1010− sendChan spBool True
1011−
1012− let rp3 = (even <$> rpInt) <|> rpBool
1013−
1014− False <- receiveChan rp3
1015− True <- receiveChan rp3
1016−
1017− -- Test Monad instance
1018−
1019− sendChan spBool True
1020− sendChan spBool False
1021− sendChan spInt 5
1022−
1023− let rp4 :: ReceivePort Int
1024− rp4 = do b <- rpBool
1025− if b
1026− then rpInt
1027− else return 7
1028−
1029− 5 <- receiveChan rp4
1030− 7 <- receiveChan rp4
1031−
1032− liftIO $ putMVar done ()
1033−
1034− takeMVar done
1035−
1036−testKillLocal :: NT.Transport -> Assertion
1037−testKillLocal transport = do
1038− localNode <- newLocalNode transport initRemoteTable
1039− done <- newEmptyMVar
1040−
1041− pid <- forkProcess localNode $ do
1042− liftIO $ threadDelay 1000000
1043−
1044− runProcess localNode $ do
1045− ref <- monitor pid
1046− us <- getSelfPid
1047− kill pid "TestKill"
1048− ProcessMonitorNotification ref' pid' (DiedException ex) <- expect
1049− True <- return $ ref == ref' && pid == pid' && ex == "killed-by=" ++ show us ++ ",reason=TestKill"
1050− liftIO $ putMVar done ()
1051−
1052− takeMVar done
1053−
1054−testKillRemote :: NT.Transport -> Assertion
1055−testKillRemote transport = do
1056− node1 <- newLocalNode transport initRemoteTable
1057− node2 <- newLocalNode transport initRemoteTable
1058− done <- newEmptyMVar
1059−
1060− pid <- forkProcess node1 $ do
1061− liftIO $ threadDelay 1000000
1062−
1063− runProcess node2 $ do
1064− ref <- monitor pid
1065− us <- getSelfPid
1066− kill pid "TestKill"
1067− ProcessMonitorNotification ref' pid' (DiedException reason) <- expect
1068− True <- return $ ref == ref' && pid == pid' && reason == "killed-by=" ++ show us ++ ",reason=TestKill"
1069− liftIO $ putMVar done ()
1070−
1071− takeMVar done
1072−
1073−testCatchesExit :: NT.Transport -> Assertion
1074−testCatchesExit transport = do
1075− localNode <- newLocalNode transport initRemoteTable
1076− done <- newEmptyMVar
1077−
1078− _ <- forkProcess localNode $ do
1079− (die ("foobar", 123 :: Int))
1080− `catchesExit` [
1081− (\_ m -> handleMessage m (\(_ :: String) -> return ()))
1082− , (\_ m -> handleMessage m (\(_ :: Maybe Int) -> return ()))
1083− , (\_ m -> handleMessage m (\(_ :: String, _ :: Int)
1084− -> (liftIO $ putMVar done ()) >> return ()))
1085− ]
1086−
1087− takeMVar done
1088−
1089−testHandleMessageIf :: NT.Transport -> Assertion
1090−testHandleMessageIf transport = do
1091− localNode <- newLocalNode transport initRemoteTable
1092− done <- newEmptyMVar
1093− _ <- forkProcess localNode $ do
1094− self <- getSelfPid
1095− send self (5 :: Integer, 10 :: Integer)
1096− msg <- receiveWait [ matchMessage return ]
1097− Nothing <- handleMessageIf msg (\() -> True) (\() -> die $ "whoops")
1098− handleMessageIf msg (\(x :: Integer, y :: Integer) -> x == 5 && y == 10)
1099− (\input -> liftIO $ putMVar done input)
1100− return ()
1101−
1102− result <- takeMVar done
1103− expectThat result $ equalTo (5, 10)
1104−
1105−testCatches :: NT.Transport -> Assertion
1106−testCatches transport = do
1107− localNode <- newLocalNode transport initRemoteTable
1108− done <- newEmptyMVar
1109−
1110− _ <- forkProcess localNode $ do
1111− node <- getSelfNode
1112− (liftIO $ throwIO (ProcessLinkException (nullProcessId node) DiedNormal))
1113− `catches` [
1114− Handler (\(ProcessLinkException _ _) -> liftIO $ putMVar done ())
1115− ]
1116−
1117− takeMVar done
1118−
1119−testMaskRestoreScope :: NT.Transport -> Assertion
1120−testMaskRestoreScope transport = do
1121− localNode <- newLocalNode transport initRemoteTable
1122− parentPid <- newEmptyMVar :: IO (MVar ProcessId)
1123− spawnedPid <- newEmptyMVar :: IO (MVar ProcessId)
1124−
1125− void $ runProcess localNode $ mask $ \unmask -> do
1126− getSelfPid >>= liftIO . putMVar parentPid
1127− void $ spawnLocal $ unmask (getSelfPid >>= liftIO . putMVar spawnedPid)
1128−
1129− parent <- liftIO $ takeMVar parentPid
1130− child <- liftIO $ takeMVar spawnedPid
1131− expectThat parent $ isNot $ equalTo child
1132−
1133−testDie :: NT.Transport -> Assertion
1134−testDie transport = do
1135− localNode <- newLocalNode transport initRemoteTable
1136− done <- newEmptyMVar
1137−
1138− _ <- forkProcess localNode $ do
1139− (die ("foobar", 123 :: Int))
1140− `catchExit` \_from reason -> do
1141− -- TODO: should verify that 'from' has the right value
1142− True <- return $ reason == ("foobar", 123 :: Int)
1143− liftIO $ putMVar done ()
1144−
1145− takeMVar done
1146−
1147−testPrettyExit :: NT.Transport -> Assertion
1148−testPrettyExit transport = do
1149− localNode <- newLocalNode transport initRemoteTable
1150− done <- newEmptyMVar
1151−
1152− _ <- forkProcess localNode $ do
1153− (die "timeout")
1154− `catch` \ex@(ProcessExitException from _) ->
1155− let expected = "exit-from=" ++ (show from)
1156− in do
1157− True <- return $ (show ex) == expected
1158− liftIO $ putMVar done ()
1159−
1160− takeMVar done
1161−
1162−testExitLocal :: NT.Transport -> Assertion
1163−testExitLocal transport = do
1164− localNode <- newLocalNode transport initRemoteTable
1165− supervisedDone <- newEmptyMVar
1166− supervisorDone <- newEmptyMVar
1167−
1168− pid <- forkProcess localNode $ do
1169− (liftIO $ threadDelay 100000)
1170− `catchExit` \_from reason -> do
1171− -- TODO: should verify that 'from' has the right value
1172− True <- return $ reason == "TestExit"
1173− liftIO $ putMVar supervisedDone ()
1174−
1175− runProcess localNode $ do
1176− ref <- monitor pid
1177− exit pid "TestExit"
1178− -- This time the client catches the exception, so it dies normally
1179− ProcessMonitorNotification ref' pid' DiedNormal <- expect
1180− True <- return $ ref == ref' && pid == pid'
1181− liftIO $ putMVar supervisorDone ()
1182−
1183− takeMVar supervisedDone
1184− takeMVar supervisorDone
1185−
1186−testExitRemote :: NT.Transport -> Assertion
1187−testExitRemote transport = do
1188− node1 <- newLocalNode transport initRemoteTable
1189− node2 <- newLocalNode transport initRemoteTable
1190− supervisedDone <- newEmptyMVar
1191− supervisorDone <- newEmptyMVar
1192−
1193− pid <- forkProcess node1 $ do
1194− (liftIO $ threadDelay 100000)
1195− `catchExit` \_from reason -> do
1196− -- TODO: should verify that 'from' has the right value
1197− True <- return $ reason == "TestExit"
1198− liftIO $ putMVar supervisedDone ()
1199−
1200− runProcess node2 $ do
1201− ref <- monitor pid
1202− exit pid "TestExit"
1203− ProcessMonitorNotification ref' pid' DiedNormal <- expect
1204− True <- return $ ref == ref' && pid == pid'
1205− liftIO $ putMVar supervisorDone ()
1206−
1207− takeMVar supervisedDone
1208− takeMVar supervisorDone
1209−
1210−testUnsafeSend :: NT.Transport -> Assertion
1211−testUnsafeSend transport = do
1212− serverAddr <- newEmptyMVar
1213− clientDone <- newEmptyMVar
1214−
1215− localNode <- newLocalNode transport initRemoteTable
1216− void $ forkProcess localNode $ do
1217− self <- getSelfPid
1218− liftIO $ putMVar serverAddr self
1219− clientAddr <- expect
1220− unsafeSend clientAddr ()
1221−
1222− void $ forkProcess localNode $ do
1223− serverPid <- liftIO $ takeMVar serverAddr
1224− getSelfPid >>= unsafeSend serverPid
1225− () <- expect
1226− liftIO $ putMVar clientDone ()
1227−
1228− takeMVar clientDone
1229−
1230−testUnsafeNSend :: NT.Transport -> Assertion
1231−testUnsafeNSend transport = do
1232− clientDone <- newEmptyMVar
1233−
1234− localNode <- newLocalNode transport initRemoteTable
1235−
1236− pid <- forkProcess localNode $ do
1237− () <- expect
1238− liftIO $ putMVar clientDone ()
1239−
1240− void $ runProcess localNode $ do
1241− register "foobar" pid
1242− unsafeNSend "foobar" ()
1243−
1244− takeMVar clientDone
1245−
1246−testUnsafeSendChan :: NT.Transport -> Assertion
1247−testUnsafeSendChan transport = do
1248− serverAddr <- newEmptyMVar
1249− clientDone <- newEmptyMVar
1250−
1251− localNode <- newLocalNode transport initRemoteTable
1252− void $ forkProcess localNode $ do
1253− self <- getSelfPid
1254− liftIO $ putMVar serverAddr self
1255− sp <- expect
1256− unsafeSendChan sp ()
1257−
1258− void $ forkProcess localNode $ do
1259− serverPid <- liftIO $ takeMVar serverAddr
1260− (sp, rp) <- newChan
1261− unsafeSend serverPid sp
1262− () <- receiveChan rp
1263− liftIO $ putMVar clientDone ()
1264−
1265− takeMVar clientDone
1266−
1267−tests :: (NT.Transport, TransportInternals) -> [Test]
1268−tests (transport, transportInternals) = [
1269− testGroup "Basic features" [
1270− testCase "Ping" (testPing transport)
1271− , testCase "Math" (testMath transport)
1272− , testCase "Timeout" (testTimeout transport)
1273− , testCase "Timeout0" (testTimeout0 transport)
1274− , testCase "SendToTerminated" (testSendToTerminated transport)
1275− , testCase "TypedChannnels" (testTypedChannels transport)
1276− , testCase "MergeChannels" (testMergeChannels transport)
1277− , testCase "Terminate" (testTerminate transport)
1278− , testCase "Registry" (testRegistry transport)
1279− , testCase "RemoteRegistry" (testRemoteRegistry transport)
1280− , testCase "SpawnLocal" (testSpawnLocal transport)
1281− , testCase "HandleMessageIf" (testHandleMessageIf transport)
1282− , testCase "MatchAny" (testMatchAny transport)
1283− , testCase "MatchAnyHandle" (testMatchAnyHandle transport)
1284− , testCase "MatchAnyNoHandle" (testMatchAnyNoHandle transport)
1285− , testCase "MatchAnyIf" (testMatchAnyIf transport)
1286− , testCase "MatchMessageUnwrap" (testMatchMessageWithUnwrap transport)
1287− , testCase "ReceiveChanTimeout" (testReceiveChanTimeout transport)
1288− , testCase "ReceiveChanFeatures" (testReceiveChanFeatures transport)
1289− , testCase "KillLocal" (testKillLocal transport)
1290− , testCase "KillRemote" (testKillRemote transport)
1291− , testCase "Die" (testDie transport)
1292− , testCase "PrettyExit" (testPrettyExit transport)
1293− , testCase "CatchesExit" (testCatchesExit transport)
1294− , testCase "Catches" (testCatches transport)
1295− , testCase "MaskRestoreScope" (testMaskRestoreScope transport)
1296− , testCase "ExitLocal" (testExitLocal transport)
1297− , testCase "ExitRemote" (testExitRemote transport)
1298− -- Unsafe Primitives
1299− , testCase "TestUnsafeSend" (testUnsafeSend transport)
1300− , testCase "TestUnsafeNSend" (testUnsafeNSend transport)
1301− , testCase "TestUnsafeSendChan" (testUnsafeSendChan transport)
1302− ]
1303− , testGroup "Monitoring and Linking" [
1304− -- Monitoring processes
1305− --
1306− -- The "missing" combinations in the list below don't make much sense, as
1307− -- we cannot guarantee that the monitor reply or link exception will not
1308− -- happen before the unmonitor or unlink
1309− testCase "MonitorUnreachable" (testMonitorUnreachable transport True False)
1310− , testCase "MonitorNormalTermination" (testMonitorNormalTermination transport True False)
1311− , testCase "MonitorAbnormalTermination" (testMonitorAbnormalTermination transport True False)
1312− , testCase "MonitorLocalDeadProcess" (testMonitorLocalDeadProcess transport True False)
1313− , testCase "MonitorRemoteDeadProcess" (testMonitorRemoteDeadProcess transport True False)
1314− , testCase "MonitorDisconnect" (testMonitorDisconnect transport True False)
1315− , testCase "LinkUnreachable" (testMonitorUnreachable transport False False)
1316− , testCase "LinkNormalTermination" (testMonitorNormalTermination transport False False)
1317− , testCase "LinkAbnormalTermination" (testMonitorAbnormalTermination transport False False)
1318− , testCase "LinkLocalDeadProcess" (testMonitorLocalDeadProcess transport False False)
1319− , testCase "LinkRemoteDeadProcess" (testMonitorRemoteDeadProcess transport False False)
1320− , testCase "LinkDisconnect" (testMonitorDisconnect transport False False)
1321− , testCase "UnmonitorNormalTermination" (testMonitorNormalTermination transport True True)
1322− , testCase "UnmonitorAbnormalTermination" (testMonitorAbnormalTermination transport True True)
1323− , testCase "UnmonitorDisconnect" (testMonitorDisconnect transport True True)
1324− , testCase "UnlinkNormalTermination" (testMonitorNormalTermination transport False True)
1325− , testCase "UnlinkAbnormalTermination" (testMonitorAbnormalTermination transport False True)
1326− , testCase "UnlinkDisconnect" (testMonitorDisconnect transport False True)
1327− -- Monitoring nodes and channels
1328− , testCase "MonitorNode" (testMonitorNode transport)
1329− , testCase "MonitorLiveNode" (testMonitorLiveNode transport)
1330− , testCase "MonitorChannel" (testMonitorChannel transport)
1331− -- Reconnect
1332− , testCase "Reconnect" (testReconnect transport transportInternals)
1333− ]
1334− ]
1335−
1336−main :: IO ()
1337−main = do
1338− Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
1339− defaultMain (tests transport)
tests/TestClosure.hsdeleted−480
@@ -1,480 +0,0 @@
1−{-# LANGUAGE TemplateHaskell #-}
2−module Main where
3−
4−import Data.ByteString.Lazy (empty)
5−import Data.Typeable (Typeable)
6−import Control.Monad (join, replicateM, forever, replicateM_, void)
7−import Control.Exception (IOException, throw)
8−import Control.Concurrent (forkIO, threadDelay)
9−import Control.Concurrent.MVar
10− ( MVar
11− , newEmptyMVar
12− , readMVar
13− , takeMVar
14− , putMVar
15− , modifyMVar_
16− , newMVar
17− )
18−import Control.Applicative ((<$>))
19−import System.Random (randomIO)
20−import Network.Transport (Transport)
21−import Network.Transport.TCP
22− ( createTransportExposeInternals
23− , defaultTCPParameters
24− , TransportInternals(socketBetween)
25− )
26−import Network.Socket (sClose)
27−import Control.Distributed.Process
28−import Control.Distributed.Process.Closure
29−import Control.Distributed.Process.Node
30−import Control.Distributed.Process.Internal.Types (NodeId(nodeAddress))
31−import Control.Distributed.Static (staticLabel, staticClosure)
32−
33−import Test.HUnit (Assertion)
34−import Test.Framework (Test, defaultMain)
35−import Test.Framework.Providers.HUnit (testCase)
36−
37−--------------------------------------------------------------------------------
38−-- Supporting definitions --
39−--------------------------------------------------------------------------------
40−
41−quintuple :: a -> b -> c -> d -> e -> (a, b, c, d, e)
42−quintuple a b c d e = (a, b, c, d, e)
43−
44−sdictInt :: SerializableDict Int
45−sdictInt = SerializableDict
46−
47−factorial :: Int -> Process Int
48−factorial 0 = return 1
49−factorial n = (n *) <$> factorial (n - 1)
50−
51−addInt :: Int -> Int -> Int
52−addInt x y = x + y
53−
54−putInt :: Int -> MVar Int -> IO ()
55−putInt = flip putMVar
56−
57−sendPid :: ProcessId -> Process ()
58−sendPid toPid = do
59− fromPid <- getSelfPid
60− send toPid fromPid
61−
62−wait :: Int -> Process ()
63−wait = liftIO . threadDelay
64−
65−isPrime :: Integer -> Process Bool
66−isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..]
67− where
68− sieve :: [Integer] -> [Integer]
69− sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]
70− sieve [] = error "Uh oh -- we've run out of primes"
71−
72−-- | First argument indicates empty closure environment
73−typedPingServer :: () -> ReceivePort (SendPort ()) -> Process ()
74−typedPingServer () rport = forever $ do
75− sport <- receiveChan rport
76− sendChan sport ()
77−
78−signal :: ProcessId -> Process ()
79−signal pid = send pid ()
80−
81−remotable [ 'factorial
82− , 'addInt
83− , 'putInt
84− , 'sendPid
85− , 'sdictInt
86− , 'wait
87− , 'typedPingServer
88− , 'isPrime
89− , 'quintuple
90− , 'signal
91− ]
92−
93−randomElement :: [a] -> IO a
94−randomElement xs = do
95− ix <- randomIO
96− return (xs !! (ix `mod` length xs))
97−
98−remotableDecl [
99− [d| dfib :: ([NodeId], SendPort Integer, Integer) -> Process () ;
100− dfib (_, reply, 0) = sendChan reply 0
101− dfib (_, reply, 1) = sendChan reply 1
102− dfib (nids, reply, n) = do
103− nid1 <- liftIO $ randomElement nids
104− nid2 <- liftIO $ randomElement nids
105− (sport, rport) <- newChan
106− spawn nid1 $ $(mkClosure 'dfib) (nids, sport, n - 2)
107− spawn nid2 $ $(mkClosure 'dfib) (nids, sport, n - 1)
108− n1 <- receiveChan rport
109− n2 <- receiveChan rport
110− sendChan reply $ n1 + n2
111− |]
112− ]
113−
114−-- Just try creating a static polymorphic value
115−staticQuintuple :: (Typeable a, Typeable b, Typeable c, Typeable d, Typeable e)
116− => Static (a -> b -> c -> d -> e -> (a, b, c, d, e))
117−staticQuintuple = $(mkStatic 'quintuple)
118−
119−factorialClosure :: Int -> Closure (Process Int)
120−factorialClosure = $(mkClosure 'factorial)
121−
122−addIntClosure :: Int -> Closure (Int -> Int)
123−addIntClosure = $(mkClosure 'addInt)
124−
125−putIntClosure :: Int -> Closure (MVar Int -> IO ())
126−putIntClosure = $(mkClosure 'putInt)
127−
128−sendPidClosure :: ProcessId -> Closure (Process ())
129−sendPidClosure = $(mkClosure 'sendPid)
130−
131−sendFac :: Int -> ProcessId -> Closure (Process ())
132−sendFac n pid = factorialClosure n `bindCP` cpSend $(mkStatic 'sdictInt) pid
133−
134−factorialOf :: Closure (Int -> Process Int)
135−factorialOf = staticClosure $(mkStatic 'factorial)
136−
137−factorial' :: Int -> Closure (Process Int)
138−factorial' n = returnCP $(mkStatic 'sdictInt) n `bindCP` factorialOf
139−
140−waitClosure :: Int -> Closure (Process ())
141−waitClosure = $(mkClosure 'wait)
142−
143−simulateNetworkFailure :: TransportInternals -> NodeId -> NodeId -> Process ()
144−simulateNetworkFailure transportInternals fr to = liftIO $ do
145− threadDelay 10000
146− sock <- socketBetween transportInternals (nodeAddress fr) (nodeAddress to)
147− sClose sock
148− threadDelay 10000
149−
150−--------------------------------------------------------------------------------
151−-- The tests proper --
152−--------------------------------------------------------------------------------
153−
154−testUnclosure :: Transport -> RemoteTable -> Assertion
155−testUnclosure transport rtable = do
156− node <- newLocalNode transport rtable
157− done <- newEmptyMVar
158− forkProcess node $ do
159− 120 <- join . unClosure $ factorialClosure 5
160− liftIO $ putMVar done ()
161− takeMVar done
162−
163−testBind :: Transport -> RemoteTable -> Assertion
164−testBind transport rtable = do
165− node <- newLocalNode transport rtable
166− done <- newEmptyMVar
167− runProcess node $ do
168− us <- getSelfPid
169− join . unClosure $ sendFac 6 us
170− (720 :: Int) <- expect
171− liftIO $ putMVar done ()
172− takeMVar done
173−
174−testSendPureClosure :: Transport -> RemoteTable -> Assertion
175−testSendPureClosure transport rtable = do
176− serverAddr <- newEmptyMVar
177− serverDone <- newEmptyMVar
178−
179− forkIO $ do
180− node <- newLocalNode transport rtable
181− addr <- forkProcess node $ do
182− cl <- expect
183− fn <- unClosure cl :: Process (Int -> Int)
184− 13 <- return $ fn 6
185− liftIO $ putMVar serverDone ()
186− putMVar serverAddr addr
187−
188− forkIO $ do
189− node <- newLocalNode transport rtable
190− theirAddr <- readMVar serverAddr
191− runProcess node $ send theirAddr (addIntClosure 7)
192−
193− takeMVar serverDone
194−
195−testSendIOClosure :: Transport -> RemoteTable -> Assertion
196−testSendIOClosure transport rtable = do
197− serverAddr <- newEmptyMVar
198− serverDone <- newEmptyMVar
199−
200− forkIO $ do
201− node <- newLocalNode transport rtable
202− addr <- forkProcess node $ do
203− cl <- expect
204− io <- unClosure cl :: Process (MVar Int -> IO ())
205− liftIO $ do
206− someMVar <- newEmptyMVar
207− io someMVar
208− 5 <- readMVar someMVar
209− putMVar serverDone ()
210− putMVar serverAddr addr
211−
212− forkIO $ do
213− node <- newLocalNode transport rtable
214− theirAddr <- readMVar serverAddr
215− runProcess node $ send theirAddr (putIntClosure 5)
216−
217− takeMVar serverDone
218−
219−testSendProcClosure :: Transport -> RemoteTable -> Assertion
220−testSendProcClosure transport rtable = do
221− serverAddr <- newEmptyMVar
222− clientDone <- newEmptyMVar
223−
224− forkIO $ do
225− node <- newLocalNode transport rtable
226− addr <- forkProcess node $ do
227− cl <- expect
228− pr <- unClosure cl :: Process (Int -> Process ())
229− pr 5
230− putMVar serverAddr addr
231−
232− forkIO $ do
233− node <- newLocalNode transport rtable
234− theirAddr <- readMVar serverAddr
235− runProcess node $ do
236− pid <- getSelfPid
237− send theirAddr (cpSend $(mkStatic 'sdictInt) pid)
238− 5 <- expect :: Process Int
239− liftIO $ putMVar clientDone ()
240−
241− takeMVar clientDone
242−
243−testSpawn :: Transport -> RemoteTable -> Assertion
244−testSpawn transport rtable = do
245− serverNodeAddr <- newEmptyMVar
246− clientDone <- newEmptyMVar
247−
248− forkIO $ do
249− node <- newLocalNode transport rtable
250− putMVar serverNodeAddr (localNodeId node)
251−
252− forkIO $ do
253− node <- newLocalNode transport rtable
254− nid <- readMVar serverNodeAddr
255− runProcess node $ do
256− pid <- getSelfPid
257− pid' <- spawn nid (sendPidClosure pid)
258− pid'' <- expect
259− True <- return $ pid' == pid''
260− liftIO $ putMVar clientDone ()
261−
262− takeMVar clientDone
263−
264−testCall :: Transport -> RemoteTable -> Assertion
265−testCall transport rtable = do
266− serverNodeAddr <- newEmptyMVar
267− clientDone <- newEmptyMVar
268−
269− forkIO $ do
270− node <- newLocalNode transport rtable
271− putMVar serverNodeAddr (localNodeId node)
272−
273− forkIO $ do
274− node <- newLocalNode transport rtable
275− nid <- readMVar serverNodeAddr
276− runProcess node $ do
277− (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorialClosure 5)
278− liftIO $ putMVar clientDone ()
279−
280− takeMVar clientDone
281−
282−testCallBind :: Transport -> RemoteTable -> Assertion
283−testCallBind transport rtable = do
284− serverNodeAddr <- newEmptyMVar
285− clientDone <- newEmptyMVar
286−
287− forkIO $ do
288− node <- newLocalNode transport rtable
289− putMVar serverNodeAddr (localNodeId node)
290−
291− forkIO $ do
292− node <- newLocalNode transport rtable
293− nid <- readMVar serverNodeAddr
294− runProcess node $ do
295− (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorial' 5)
296− liftIO $ putMVar clientDone ()
297−
298− takeMVar clientDone
299−
300−testSeq :: Transport -> RemoteTable -> Assertion
301−testSeq transport rtable = do
302− node <- newLocalNode transport rtable
303− done <- newEmptyMVar
304− runProcess node $ do
305− us <- getSelfPid
306− join . unClosure $ sendFac 5 us `seqCP` sendFac 6 us
307− 120 :: Int <- expect
308− 720 :: Int <- expect
309− liftIO $ putMVar done ()
310− takeMVar done
311−
312−-- Test 'spawnSupervised'
313−--
314−-- Set up a supervisor, spawn a child, then have a third process monitor the
315−-- child. The supervisor then throws an exception, the child dies because it
316−-- was linked to the supervisor, and the third process notices that the child
317−-- dies.
318−testSpawnSupervised :: Transport -> RemoteTable -> Assertion
319−testSpawnSupervised transport rtable = do
320− [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
321− [superPid, childPid] <- replicateM 2 $ newEmptyMVar
322− thirdProcessDone <- newEmptyMVar
323−
324− forkProcess node1 $ do
325− us <- getSelfPid
326− liftIO $ putMVar superPid us
327− (child, _ref) <- spawnSupervised (localNodeId node2) (waitClosure 1000000)
328− liftIO $ do
329− putMVar childPid child
330− threadDelay 500000 -- Give the child a chance to link to us
331− throw supervisorDeath
332−
333− forkProcess node2 $ do
334− [super, child] <- liftIO $ mapM readMVar [superPid, childPid]
335− ref <- monitor child
336− ProcessMonitorNotification ref' pid' (DiedException e) <- expect
337− True <- return $ ref' == ref
338− && pid' == child
339− && e == show (ProcessLinkException super (DiedException (show supervisorDeath)))
340− liftIO $ putMVar thirdProcessDone ()
341−
342− takeMVar thirdProcessDone
343− where
344− supervisorDeath :: IOException
345− supervisorDeath = userError "Supervisor died"
346−
347−testSpawnInvalid :: Transport -> RemoteTable -> Assertion
348−testSpawnInvalid transport rtable = do
349− node <- newLocalNode transport rtable
350− done <- newEmptyMVar
351− forkProcess node $ do
352− (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)
353− ProcessMonitorNotification ref' pid' _reason <- expect
354− -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure
355− True <- return $ ref' == ref && pid == pid'
356− liftIO $ putMVar done ()
357− takeMVar done
358−
359−testClosureExpect :: Transport -> RemoteTable -> Assertion
360−testClosureExpect transport rtable = do
361− node <- newLocalNode transport rtable
362− done <- newEmptyMVar
363− runProcess node $ do
364− nodeId <- getSelfNode
365− us <- getSelfPid
366− them <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us
367− send them (1234 :: Int)
368− (1234 :: Int) <- expect
369− liftIO $ putMVar done ()
370− takeMVar done
371−
372−testSpawnChannel :: Transport -> RemoteTable -> Assertion
373−testSpawnChannel transport rtable = do
374− done <- newEmptyMVar
375− [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
376−
377− forkProcess node1 $ do
378− pingServer <- spawnChannel
379− (sdictSendPort sdictUnit)
380− (localNodeId node2)
381− ($(mkClosure 'typedPingServer) ())
382− (sendReply, receiveReply) <- newChan
383− sendChan pingServer sendReply
384− receiveChan receiveReply
385− liftIO $ putMVar done ()
386−
387− takeMVar done
388−
389−testTDict :: Transport -> RemoteTable -> Assertion
390−testTDict transport rtable = do
391− done <- newEmptyMVar
392− [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
393− forkProcess node1 $ do
394− True <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))
395− liftIO $ putMVar done ()
396− takeMVar done
397−
398−testFib :: Transport -> RemoteTable -> Assertion
399−testFib transport rtable = do
400− nodes <- replicateM 4 $ newLocalNode transport rtable
401− done <- newEmptyMVar
402−
403− forkProcess (head nodes) $ do
404− (sport, rport) <- newChan
405− spawnLocal $ dfib (map localNodeId nodes, sport, 10)
406− 55 <- receiveChan rport :: Process Integer
407− liftIO $ putMVar done ()
408−
409− takeMVar done
410−
411−testSpawnReconnect :: Transport -> RemoteTable -> TransportInternals -> Assertion
412−testSpawnReconnect transport rtable transportInternals = do
413− [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
414− let nid1 = localNodeId node1
415− nid2 = localNodeId node2
416− done <- newEmptyMVar
417− iv <- newMVar (0 :: Int)
418−
419− incr <- forkProcess node1 $ forever $ do
420− () <- expect
421− liftIO $ modifyMVar_ iv (return . (+ 1))
422−
423− forkProcess node2 $ do
424− _pid1 <- spawn nid1 ($(mkClosure 'signal) incr)
425− simulateNetworkFailure transportInternals nid2 nid1
426− _pid2 <- spawn nid1 ($(mkClosure 'signal) incr)
427− _pid3 <- spawn nid1 ($(mkClosure 'signal) incr)
428−
429− liftIO $ threadDelay 100000
430−
431− count <- liftIO $ takeMVar iv
432− True <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'
433−
434− liftIO $ putMVar done ()
435−
436− takeMVar done
437−
438−-- | 'spawn' used to ave a race condition which would be triggered if the
439−-- spawning process terminates immediately after spawning
440−testSpawnTerminate :: Transport -> RemoteTable -> Assertion
441−testSpawnTerminate transport rtable = do
442− slave <- newLocalNode transport rtable
443− master <- newLocalNode transport rtable
444− masterDone <- newEmptyMVar
445−
446− runProcess master $ do
447− us <- getSelfPid
448− replicateM_ 1000 . spawnLocal . void . spawn (localNodeId slave) $ $(mkClosure 'signal) us
449− replicateM_ 1000 $ (expect :: Process ())
450− liftIO $ putMVar masterDone ()
451−
452− takeMVar masterDone
453−
454−tests :: (Transport, TransportInternals) -> RemoteTable -> [Test]
455−tests (transport, transportInternals) rtable = [
456− testCase "Unclosure" (testUnclosure transport rtable)
457− , testCase "Bind" (testBind transport rtable)
458− , testCase "SendPureClosure" (testSendPureClosure transport rtable)
459− , testCase "SendIOClosure" (testSendIOClosure transport rtable)
460− , testCase "SendProcClosure" (testSendProcClosure transport rtable)
461− , testCase "Spawn" (testSpawn transport rtable)
462− , testCase "Call" (testCall transport rtable)
463− , testCase "CallBind" (testCallBind transport rtable)
464− , testCase "Seq" (testSeq transport rtable)
465− , testCase "SpawnSupervised" (testSpawnSupervised transport rtable)
466− , testCase "SpawnInvalid" (testSpawnInvalid transport rtable)
467− , testCase "ClosureExpect" (testClosureExpect transport rtable)
468− , testCase "SpawnChannel" (testSpawnChannel transport rtable)
469− , testCase "TDict" (testTDict transport rtable)
470− , testCase "Fib" (testFib transport rtable)
471− , testCase "SpawnTerminate" (testSpawnTerminate transport rtable)
472− , testCase "SpawnReconnect" (testSpawnReconnect transport rtable transportInternals)
473− ]
474−
475−
476−main :: IO ()
477−main = do
478− Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
479− let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable
480− defaultMain (tests transport rtable)