git.iko.soy/distributed-process
on master help sign in
.travis.yml+31−26
since splitsnapshots
unifiedsplit
@@ -4,35 +4,40 @@ sudo: false
4
4
5matrix:
5matrix:
6 include:
6 include:
7− - env: CABALVER=1.22 GHCVER=7.4.1
7+ - env: ARGS="--stack-yaml stack-ghc-7.10.3.yaml"
8− addons: {apt: {packages: [cabal-install-1.22,ghc-7.4.1], sources: [hvr-ghc]}}
8+ addons: {apt: {packages: [libgmp-dev]}}
9− - env: CABALVER=1.22 GHCVER=7.6.3
9+ - env: ARGS="--stack-yaml stack-ghc-8.2.2.yaml"
10− addons: {apt: {packages: [cabal-install-1.22,ghc-7.6.3], sources: [hvr-ghc]}}
10+ addons: {apt: {packages: [libgmp-dev]}}
11− - env: CABALVER=1.22 GHCVER=7.8.4
11+ - env: ARGS="--stack-yaml stack-ghc-8.4.4.yaml"
12− addons: {apt: {packages: [cabal-install-1.22,ghc-7.8.4], sources: [hvr-ghc]}}
12+ addons: {apt: {packages: [libgmp-dev]}}
13− - env: CABALVER=1.22 GHCVER=7.10.2
13+ - env: ARGS="--stack-yaml stack-nightly.yaml --resolver nightly"
14− addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.2],sources: [hvr-ghc]}}
14+ addons: {apt: {packages: [libgmp-dev]}}
15+
16+cache:
17+ directories:
18+ - $HOME/.stack
15
19
16before_install:
20before_install:
17− - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:~/.cabal/bin:$PATH
21+- export PATH=$HOME/.local/bin:$PATH;
18− - ghc --version
22+ mkdir -p ~/.local/bin;
19− - cabal --version
23+ travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack';
20− - cabal update
24+ stack --version
21− # workaround for https://ghc.haskell.org/trac/ghc/ticket/9221
22− # taken from https://github.com/hvr/multi-ghc-travis/blob/0fa68f78c2b1b059f904c9abc85510a3bb4f57e2/README.md
23− - sed -i 's/^jobs:/-- jobs:/' $HOME/.cabal/config
24
25
25install:
26install:
26− - cabal install --jobs=2 --enable-tests --only-dependencies . distributed-process-tests/
27+- stack $ARGS setup --no-terminal;
28+ stack $ARGS build --no-terminal --only-snapshot --test --no-run-tests;
27
29
28script:
30script:
29− - cabal install --jobs=2
31+- export ARG='--test-arguments'
30− - cd distributed-process-tests/
32+ export TEST_PACKAGE="distributed-process-tests:"
31− - cabal configure --enable-tests
33+- stack ${ARGS} test $ARG='--plain -t "!Flaky"' ${TEST_PACKAGE}TestCHInMemory
32− - cabal build --jobs=2
34+- stack ${ARGS} test $ARG='--plain -t "!Flaky"' ${TEST_PACKAGE}TestCHInTCP
33− # skip intermittent tests Reconnect and SpawnReconnect
35+- stack ${ARGS} test $ARG='--plain -t "!SpawnReconnect"' ${TEST_PACKAGE}TestClosure
34− - dist/build/TestCHInMemory/TestCHInMemory --plain -t '!Flaky'
36+- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestStats
35− - dist/build/TestClosure/TestClosure --plain -t '!SpawnReconnect'
37+- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestMxInMemory
36− - dist/build/TestStats/TestStats --plain
38+- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestMxInTCP
37− - dist/build/TestMx/TestMx --plain
39+- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestTracingInMemory
38− - dist/build/TestTracing/TestTracing --plain
40+
41+notifications:
42+ slack:
43+ secure: LbqbNjK1KiHNiQo/i/Za4vkMD9l9mhmN+PWYFBQkNvGFWffSRayRKFrTqt4znV2p7h4dY1XZFoC7T5RemBVQLq2ppZAUWxkITeu1OUlnmVLDfJLeVYGTR/fn90nP6Y5ITE7T3A07nKmWaRbKpIgFBAOjzgqaM0csscDx3z0WW18=
ChangeLog+100
@@ -1,3 +1,103 @@
1+[Unrealeased]
2+
3+*
4+
5+2018-06-12 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.4
6+
7+* Added support for exceptions >= 0.10
8+
9+2017-08-31 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.3
10+
11+* Drop support for ghc-7.8.* and earlier.
12+
13+2017-08-31 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.2
14+
15+* Fixed build errors with ghc-8.2.1.
16+
17+2017-08-22 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.1
18+
19+* Relax upper bounds in dependencies to build with ghc-8.2.1.
20+
21+2017-08-21 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.0
22+
23+* Change type of message sent by `say` from a 3-tuple to a proper
24+type (`SayMessage`) with a proper `UTCTime`. (#291)
25+* Expose the MonitorRef in the withMonitor call.
26+* Have unmonitor remove the monitor message in the inbox. (#268)
27+* Remove Mx Data Tables. This API isn't used, is easy to replace with various
28+other packages. (#276)
29+* Add Ord instance to SpawnRef.
30+
31+2016-10-13 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.6
32+
33+* Remove monitors from remote nodes when a process dies. (#295)
34+
35+2016-10-12 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.5
36+
37+* Use only one connection to communicate between NCs. (#296, #297)
38+* Improve documentation of CQueue.
39+* Implement bidirectional multimaps for links and monitors. (#293, #294)
40+* Fix various warnings. (#292)
41+* Fix some of the intermittent failures in tests.
42+* Improve error messages when node controllers receive invalid requests.
43+(#290)
44+
45+2016-06-09 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.4
46+
47+* Fixup build errors.
48+
49+2016-06-09 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.3
50+
51+* Relax template-haskell upper bound.
52+
53+2016-06-09 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.2
54+
55+* Provide compatibility with ghc-8.0.1
56+* Remove dependency on ghc-prim.
57+* Don't throw exceptions asynchronously.
58+* Bump upper bounds of dependencies.
59+* Fix exception handling in callLocal.
60+* Have spawnLocal inherit the masking state of the caller.
61+* Have nsend send unencoded messages to local processes.
62+
63+2016-03-03 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.1
64+
65+* Implement MonadCatch, MonadThrow, MonadMask for Process.
66+
67+2016-02-18 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.0
68+
69+* Have nsendRemote skip the transport for local communication.
70+* Unsafe primitives for usend and nsendRemote.
71+* Stop using the transport for local communication.
72+* Skip the transport for whereisRemoteAsync and registerRemoteAsync.
73+* Have nsendRemote skip the transport for local communication.
74+* Have runProcess forward exceptions.
75+* Reimport distributed-process-tests. d-p and d-p-test now can be kept in
76+sync.
77+* Add a stack.yaml file for building tests and d-p all at once conveniently.
78+* Implement unreliable forward (uforward).
79+* Add Functor instance for Match data type
80+* Have `spawnAsync` not use the transport in the local case.
81+* Fix monitor race in 'call'.
82+* Add compatibility with ghc-7.10: support new typeable, loosen deps, write
83+proper NFData instances, support new TH.
84+* Kill processes on a local node upon closeLocalNode.
85+* Fix getNodeStats function, see DP-97
86+* Return size of the queue in ProcessInfo.
87+* Implement unreliable send (usend).
88+* Implement MonadFix instance for Process.
89+* Introduce callLocal primitive.
90+* Prevent message loss due to timeouts in CQueue.
91+* More informative ProcessRegistrationException. Now includes the identifier
92+of the process that owns the name, if any.
93+* Avoid message loop between threads when tracing received messages.
94+
95+2015-06-15 Facundo Domínguez <facundo.dominguez@tweag.io> 0.5.5
96+
97+* Fix dependencies.
98+* Add compatibility with GHC-7.10.
99+* Fix various race conditions (DP-99, DP-103).
100+
12014-12-09 Tim Watson <watson.timothy@gmail.com> 0.5.2
1012014-12-09 Tim Watson <watson.timothy@gmail.com> 0.5.2
2
102
3* Fix docstring for `register`
103* Fix docstring for `register`
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+3−2
@@ -12,7 +12,7 @@ import Control.Monad
12import Control.Distributed.Process hiding (catch)
12import Control.Distributed.Process hiding (catch)
13import Control.Distributed.Process.Node
13import Control.Distributed.Process.Node
14import Control.Exception (catch, SomeException)
14import Control.Exception (catch, SomeException)
15−import Network.Transport.TCP (createTransport, defaultTCPParameters)
15+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
16import System.Environment
16import System.Environment
17import System.Console.GetOpt
17import System.Console.GetOpt
18
18
@@ -110,7 +110,8 @@ main = do
110 argv <- getArgs
110 argv <- getArgs
111 (opt, _) <- parseArgv argv
111 (opt, _) <- parseArgv argv
112 putStrLn $ "options: " ++ (show opt)
112 putStrLn $ "options: " ++ (show opt)
113− Right transport <- createTransport "127.0.0.1" "8090" defaultTCPParameters
113+ Right transport <- createTransport
114+ (defaultTCPAddr "127.0.0.1" "8090" ) defaultTCPParameters
114 node <- newLocalNode transport initRemoteTable
115 node <- newLocalNode transport initRemoteTable
115 catch (void $ runProcess node $ initialProcess opt)
116 catch (void $ runProcess node $ initialProcess opt)
116 (\(e :: SomeException) -> putStrLn $ "ERROR: " ++ (show e))
117 (\(e :: SomeException) -> putStrLn $ "ERROR: " ++ (show e))
benchmarks/Spawns.hs+6−4
@@ -7,7 +7,7 @@ import Control.Monad
7import Control.Applicative
7import Control.Applicative
8import Control.Distributed.Process
8import Control.Distributed.Process
9import Control.Distributed.Process.Node
9import Control.Distributed.Process.Node
10−import Network.Transport.TCP (createTransport, defaultTCPParameters)
10+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
11import Data.Binary (encode, decode)
11import Data.Binary (encode, decode)
12import qualified Data.ByteString.Lazy as BSL
12import qualified Data.ByteString.Lazy as BSL
13
13
@@ -42,6 +42,8 @@ initialProcess "CLIENT" = do
42main :: IO ()
42main :: IO ()
43main = do
43main = do
44 [role, host, port] <- getArgs
44 [role, host, port] <- getArgs
45− Right transport <- createTransport host port defaultTCPParameters
45+ trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
46+ case trans of
46− node <- newLocalNode transport initRemoteTable
47+ Right transport -> do node <- newLocalNode transport initRemoteTable
47− runProcess node $ initialProcess role
48+ runProcess node $ initialProcess role
49+ Left other -> error $ show other
benchmarks/Throughput.hs+6−4
@@ -6,7 +6,7 @@ import Control.Monad
6import Control.Applicative
6import Control.Applicative
7import Control.Distributed.Process
7import Control.Distributed.Process
8import Control.Distributed.Process.Node
8import Control.Distributed.Process.Node
9−import Network.Transport.TCP (createTransport, defaultTCPParameters)
9+import Network.Transport.TCP (createTransport, defaultTCPParameters, defaultTCPAddr)
10import Data.Binary
10import Data.Binary
11import qualified Data.ByteString.Lazy as BSL
11import qualified Data.ByteString.Lazy as BSL
12import Data.Typeable
12import Data.Typeable
@@ -67,6 +67,8 @@ initialProcess "CLIENT" = do
67main :: IO ()
67main :: IO ()
68main = do
68main = do
69 [role, host, port] <- getArgs
69 [role, host, port] <- getArgs
70− Right transport <- createTransport host port defaultTCPParameters
70+ trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
71+ case trans of
71− node <- newLocalNode transport initRemoteTable
72+ Right transport -> do node <- newLocalNode transport initRemoteTable
72− runProcess node $ initialProcess role
73+ runProcess node $ initialProcess role
74+ Left other -> error $ show other
distributed-process-tests/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
distributed-process-tests/distributed-process-tests.cabal+47−32
@@ -1,11 +1,12 @@
1name: distributed-process-tests
1name: distributed-process-tests
2−version: 0.4.2
2+version: 0.4.11
3synopsis: Tests and test support tools for distributed-process.
3synopsis: Tests and test support tools for distributed-process.
4−homepage: http://github.com/haskell-distributed/distributed-process-tests
4+homepage: http://github.com/haskell-distributed/distributed-process/tree/master/distributed-process-tests
5+description: Tests and test suite for Cloud Haskell libraries.
5license: BSD3
6license: BSD3
6license-file: LICENSE
7license-file: LICENSE
7Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
8Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
8−Maintainer: watson.timothy@gmail.com, edsko@well-typed.com, duncan@well-typed.com
9+Maintainer: Tim Watson <watson.timothy@gmail.com>
9copyright: Well-Typed LLP
10copyright: Well-Typed LLP
10category: Control, Cloud Haskell
11category: Control, Cloud Haskell
11build-type: Simple
12build-type: Simple
@@ -25,16 +26,18 @@ library
25 Control.Distributed.Process.Tests.Tracing
26 Control.Distributed.Process.Tests.Tracing
26 Control.Distributed.Process.Tests.Internal.Utils
27 Control.Distributed.Process.Tests.Internal.Utils
27 Build-Depends: base >= 4.4 && < 5,
28 Build-Depends: base >= 4.4 && < 5,
28− ansi-terminal >= 0.5 && < 0.7,
29+ ansi-terminal >= 0.5,
29− binary >= 0.5 && < 0.8,
30+ binary >= 0.5 && < 0.9,
30− bytestring >= 0.9 && < 0.11,
31+ bytestring >= 0.9 && < 0.12,
31− distributed-process >= 0.5.3 && < 0.6,
32+ distributed-process >= 0.6.0 && < 0.8,
32 distributed-static,
33 distributed-static,
34+ exceptions >= 0.5,
33− HUnit >= 1.2 && < 1.4,
35+ HUnit >= 1.2 && < 1.7,
34− network-transport >= 0.4.1.0 && < 0.5,
36+ network-transport >= 0.4.1.0 && < 0.6,
35− network >= 2.5 && < 2.7,
37+ network >= 2.5 && < 2.9,
36 random >= 1.0 && < 1.2,
38 random >= 1.0 && < 1.2,
37 rematch >= 0.1.2.1,
39 rematch >= 0.1.2.1,
40+ setenv >= 0.1.1.3,
38 test-framework >= 0.6 && < 0.9,
41 test-framework >= 0.6 && < 0.9,
39 test-framework-hunit >= 0.2.0 && < 0.4,
42 test-framework-hunit >= 0.2.0 && < 0.4,
40 stm
43 stm
@@ -46,7 +49,6 @@ library
46 DeriveDataTypeable,
49 DeriveDataTypeable,
47 DeriveGeneric,
50 DeriveGeneric,
48 GeneralizedNewtypeDeriving,
51 GeneralizedNewtypeDeriving,
49− OverlappingInstances,
50 RankNTypes,
52 RankNTypes,
51 RecordWildCards,
53 RecordWildCards,
52 ScopedTypeVariables
54 ScopedTypeVariables
@@ -59,12 +61,12 @@ Test-Suite TestCHInMemory
59 CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.CH
61 CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.CH
60 Build-Depends: base >= 4.4 && < 5,
62 Build-Depends: base >= 4.4 && < 5,
61 distributed-process-tests,
63 distributed-process-tests,
62− network >= 2.3 && < 2.7,
64+ network >= 2.3 && < 2.9,
63− network-transport >= 0.4.1.0 && < 0.5,
65+ network-transport >= 0.4.1.0 && < 0.6,
64 network-transport-inmemory >= 0.5,
66 network-transport-inmemory >= 0.5,
65 test-framework >= 0.6 && < 0.9
67 test-framework >= 0.6 && < 0.9
66 Extensions: CPP
68 Extensions: CPP
67− ghc-options: -Wall -threaded -debug -eventlog -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
69+ ghc-options: -Wall -threaded -eventlog -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
68 HS-Source-Dirs: tests
70 HS-Source-Dirs: tests
69
71
70Test-Suite TestCHInTCP
72Test-Suite TestCHInTCP
@@ -74,14 +76,14 @@ Test-Suite TestCHInTCP
74 if flag(tcp)
76 if flag(tcp)
75 Build-Depends: base >= 4.4 && < 5,
77 Build-Depends: base >= 4.4 && < 5,
76 distributed-process-tests,
78 distributed-process-tests,
77− network >= 2.3 && < 2.7,
79+ network >= 2.3 && < 2.9,
78− network-transport >= 0.4.1.0 && < 0.5,
80+ network-transport >= 0.4.1.0 && < 0.6,
79− network-transport-tcp >= 0.3 && < 0.5,
81+ network-transport-tcp >= 0.5 && < 0.7,
80 test-framework >= 0.6 && < 0.9
82 test-framework >= 0.6 && < 0.9
81 else
83 else
82 Buildable: False
84 Buildable: False
83 Extensions: CPP
85 Extensions: CPP
84− ghc-options: -Wall -threaded -debug -eventlog -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
86+ ghc-options: -Wall -threaded -eventlog -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
85 HS-Source-Dirs: tests
87 HS-Source-Dirs: tests
86
88
87
89
@@ -91,8 +93,8 @@ Test-Suite TestClosure
91 CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Closure
93 CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Closure
92 Build-Depends: base >= 4.4 && < 5,
94 Build-Depends: base >= 4.4 && < 5,
93 distributed-process-tests,
95 distributed-process-tests,
94− network >= 2.3 && < 2.7,
96+ network >= 2.3 && < 2.9,
95− network-transport >= 0.4.1.0 && < 0.5,
97+ network-transport >= 0.4.1.0 && < 0.6,
96 network-transport-inmemory >= 0.5,
98 network-transport-inmemory >= 0.5,
97 test-framework >= 0.6 && < 0.9
99 test-framework >= 0.6 && < 0.9
98 Extensions: CPP
100 Extensions: CPP
@@ -105,39 +107,52 @@ Test-Suite TestStats
105 CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Stats
107 CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Stats
106 Build-Depends: base >= 4.4 && < 5,
108 Build-Depends: base >= 4.4 && < 5,
107 distributed-process-tests,
109 distributed-process-tests,
108− network >= 2.3 && < 2.7,
110+ network >= 2.3 && < 2.9,
109− network-transport >= 0.4.1.0 && < 0.5,
111+ network-transport >= 0.4.1.0 && < 0.6,
110 network-transport-inmemory >= 0.5,
112 network-transport-inmemory >= 0.5,
111 test-framework >= 0.6 && < 0.9
113 test-framework >= 0.6 && < 0.9
112 Extensions: CPP
114 Extensions: CPP
113− ghc-options: -Wall -debug -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
115+ ghc-options: -Wall -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
114 HS-Source-Dirs: tests
116 HS-Source-Dirs: tests
115
117
116−
117−Test-Suite TestMx
118+Test-Suite TestMxInMemory
118 Type: exitcode-stdio-1.0
119 Type: exitcode-stdio-1.0
119 Main-Is: runInMemory.hs
120 Main-Is: runInMemory.hs
120 CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Mx
121 CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Mx
121 Build-Depends: base >= 4.4 && < 5,
122 Build-Depends: base >= 4.4 && < 5,
122 distributed-process-tests,
123 distributed-process-tests,
123− network >= 2.3 && < 2.7,
124+ network >= 2.3 && < 2.9,
124− network-transport >= 0.4.1.0 && < 0.5,
125+ network-transport >= 0.4.1.0 && < 0.6,
125 network-transport-inmemory >= 0.5,
126 network-transport-inmemory >= 0.5,
126 test-framework >= 0.6 && < 0.9
127 test-framework >= 0.6 && < 0.9
127 Extensions: CPP
128 Extensions: CPP
128− ghc-options: -Wall -debug -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
129+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
129 HS-Source-Dirs: tests
130 HS-Source-Dirs: tests
130
131
131−Test-Suite TestTracing
132+Test-Suite TestTracingInMemory
132 Type: exitcode-stdio-1.0
133 Type: exitcode-stdio-1.0
133 Main-Is: runInMemory.hs
134 Main-Is: runInMemory.hs
134 CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Tracing
135 CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Tracing
135 Build-Depends: base >= 4.4 && < 5,
136 Build-Depends: base >= 4.4 && < 5,
136 distributed-process-tests,
137 distributed-process-tests,
137− network >= 2.3 && < 2.7,
138+ network >= 2.3 && < 2.9,
138− network-transport >= 0.4.1.0 && < 0.5,
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,
139 network-transport-inmemory >= 0.5,
154 network-transport-inmemory >= 0.5,
140 test-framework >= 0.6 && < 0.9
155 test-framework >= 0.6 && < 0.9
141 Extensions: CPP
156 Extensions: CPP
142− ghc-options: -Wall -debug -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
157+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
143 HS-Source-Dirs: tests
158 HS-Source-Dirs: tests
distributed-process-tests/src/Control/Distributed/Process/Tests/CH.hs+529−252
@@ -9,6 +9,7 @@ import Network.Transport.Test (TestTransport(..))
9import Data.Binary (Binary(..))
9import Data.Binary (Binary(..))
10import Data.Typeable (Typeable)
10import Data.Typeable (Typeable)
11import Data.Foldable (forM_)
11import Data.Foldable (forM_)
12+import Data.Function (fix)
12import Data.IORef
13import Data.IORef
13 ( readIORef
14 ( readIORef
14 , writeIORef
15 , writeIORef
@@ -24,24 +25,32 @@ import Control.Concurrent.MVar
24 )
25 )
25import Control.Monad (replicateM_, replicateM, forever, void, unless, join)
26import Control.Monad (replicateM_, replicateM, forever, void, unless, join)
26import Control.Exception (SomeException, throwIO, ErrorCall(..))
27import Control.Exception (SomeException, throwIO, ErrorCall(..))
27−import qualified Control.Exception as Ex (catch)
28+import Control.Monad.Catch (try, catch, finally, mask, onException)
28−import Control.Applicative ((<$>), (<*>), pure, (<|>))
29+import Control.Applicative ((<|>))
29import qualified Network.Transport as NT (closeEndPoint, EndPointAddress)
30import qualified Network.Transport as NT (closeEndPoint, EndPointAddress)
30−import Control.Distributed.Process
31+import Control.Distributed.Process hiding
32+ ( try
33+ , catch
34+ , finally
35+ , mask
36+ , onException
37+ )
31import Control.Distributed.Process.Internal.Types
38import Control.Distributed.Process.Internal.Types
32− ( NodeId(nodeAddress)
33− , LocalNode(localEndPoint)
39+ ( LocalNode(localEndPoint)
34 , ProcessExitException(..)
40 , ProcessExitException(..)
35 , nullProcessId
41 , nullProcessId
36 , createUnencodedMessage
42 , createUnencodedMessage
37 )
43 )
38import Control.Distributed.Process.Node
44import 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)
39import Control.Distributed.Process.Serializable (Serializable)
48import Control.Distributed.Process.Serializable (Serializable)
40−
49+import Data.Maybe (isNothing, isJust)
41−import Test.HUnit (Assertion, assertFailure)
50+import Test.HUnit (Assertion, assertBool, assertFailure)
42import Test.Framework (Test, testGroup)
51import Test.Framework (Test, testGroup)
43import Test.Framework.Providers.HUnit (testCase)
52import Test.Framework.Providers.HUnit (testCase)
44−import Control.Rematch hiding (match)
53+import Control.Rematch hiding (match, isNothing, isJust)
45import Control.Rematch.Run (Match(..))
54import Control.Rematch.Run (Match(..))
46
55
47newtype Ping = Ping ProcessId
56newtype Ping = Ping ProcessId
@@ -64,7 +73,7 @@ expectThat a matcher = case res of
64forkTry :: IO () -> IO ThreadId
73forkTry :: IO () -> IO ThreadId
65forkTry p = do
74forkTry p = do
66 tid <- myThreadId
75 tid <- myThreadId
67− forkIO $ Ex.catch p (\e -> throwTo tid (e :: SomeException))
76+ forkIO $ catch p (\e -> throwTo tid (e :: SomeException))
68
77
69-- | The ping server from the paper
78-- | The ping server from the paper
70ping :: Process ()
79ping :: Process ()
@@ -74,12 +83,46 @@ ping = do
74 send partner (Ping self)
83 send partner (Ping self)
75 ping
84 ping
76
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+
77-- | Quick and dirty synchronous version of whereisRemoteAsync
94-- | Quick and dirty synchronous version of whereisRemoteAsync
78whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)
95whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)
79whereisRemote nid string = do
96whereisRemote nid string = do
80 whereisRemoteAsync nid string
97 whereisRemoteAsync nid string
98+ receiveWait [
81− WhereIsReply _ mPid <- expect
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"
82− return mPid
125+ return ()
83
126
84data Add = Add ProcessId Double Double deriving (Typeable)
127data Add = Add ProcessId Double Double deriving (Typeable)
85data Divide = Divide ProcessId Double Double deriving (Typeable)
128data Divide = Divide ProcessId Double Double deriving (Typeable)
@@ -154,12 +197,18 @@ monitorTestProcess theirAddr mOrL un reason monitorSetup done =
154 unmonitor ref
197 unmonitor ref
155 liftIO $ putMVar done ()
198 liftIO $ putMVar done ()
156 (False, ref) -> do
199 (False, ref) -> do
200+ receiveWait [
157− ProcessMonitorNotification ref' pid reason' <- expect
201+ match (\(ProcessMonitorNotification ref' pid reason') -> do
202+ liftIO $ do
203+ assertBool "Bad Monitor Signal"
158− True <- return $ Just ref' == ref && pid == theirAddr && mOrL && reason == reason'
204+ (Just ref' == ref && pid == theirAddr &&
205+ mOrL && reason == reason')
159− liftIO $ putMVar done ()
206+ putMVar done ())
207+ ]
160 )
208 )
161 (\(ProcessLinkException pid reason') -> do
209 (\(ProcessLinkException pid reason') -> do
210+ (liftIO $ assertBool "link exception unmatched" $
162− True <- return $ pid == theirAddr && not mOrL && not un && reason == reason'
211+ pid == theirAddr && not mOrL && not un && reason == reason')
163 liftIO $ putMVar done ()
212 liftIO $ putMVar done ()
164 )
213 )
165
214
@@ -190,8 +239,10 @@ testPing TestTransport{..} = do
190 pid <- getSelfPid
239 pid <- getSelfPid
191 replicateM_ numPings $ do
240 replicateM_ numPings $ do
192 send pingServer (Pong pid)
241 send pingServer (Pong pid)
193− Ping _ <- expect
242+ p <- expectTimeout 3000000
243+ case p of
194− return ()
244+ Just (Ping _) -> return ()
245+ Nothing -> die "Failed to receive Ping"
195
246
196 putMVar clientDone ()
247 putMVar clientDone ()
197
248
@@ -278,7 +329,7 @@ testMonitorLocalDeadProcess TestTransport{..} mOrL un = do
278 runProcess localNode $ do
329 runProcess localNode $ do
279 monitor theirAddr
330 monitor theirAddr
280 -- wait for the process to die
331 -- wait for the process to die
281− ProcessMonitorNotification _ _ _ <- expect
332+ expect :: Process ProcessMonitorNotification
282 monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
333 monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
283
334
284 takeMVar done
335 takeMVar done
@@ -352,14 +403,17 @@ testMath TestTransport{..} = do
352 runProcess localNode $ do
403 runProcess localNode $ do
353 pid <- getSelfPid
404 pid <- getSelfPid
354 send mathServer (Add pid 1 2)
405 send mathServer (Add pid 1 2)
355− 3 <- expect :: Process Double
406+ three <- expect :: Process Double
356 send mathServer (Divide pid 8 2)
407 send mathServer (Divide pid 8 2)
357− 4 <- expect :: Process Double
408+ four <- expect :: Process Double
358 send mathServer (Divide pid 8 0)
409 send mathServer (Divide pid 8 0)
359− DivByZero <- expect
410+ divByZ <- expect
360− liftIO $ putMVar clientDone ()
411+ liftIO $ putMVar clientDone (three, four, divByZ)
361
412
362− takeMVar clientDone
413+ res <- takeMVar clientDone
414+ case res of
415+ (3, 4, DivByZero) -> return ()
416+ _ -> error $ "Something went horribly wrong"
363
417
364-- | Send first message (i.e. connect) to an already terminated process
418-- | Send first message (i.e. connect) to an already terminated process
365-- (without monitoring); then send another message to a second process on
419-- (without monitoring); then send another message to a second process on
@@ -387,11 +441,9 @@ testSendToTerminated TestTransport{..} = do
387 pid <- getSelfPid
441 pid <- getSelfPid
388 send server1 "Hi"
442 send server1 "Hi"
389 send server2 (Pong pid)
443 send server2 (Pong pid)
390− Ping pid' <- expect
391− True <- return $ pid' == server2
392− liftIO $ putMVar clientDone ()
444+ expectPing clientDone
393
445
394− takeMVar clientDone
446+ verifyClient "Expected Ping from server" clientDone
395
447
396-- | Test (non-zero) timeout
448-- | Test (non-zero) timeout
397testTimeout :: TestTransport -> Assertion
449testTimeout :: TestTransport -> Assertion
@@ -400,26 +452,24 @@ testTimeout TestTransport{..} = do
400 done <- newEmptyMVar
452 done <- newEmptyMVar
401
453
402 runProcess localNode $ do
454 runProcess localNode $ do
403− Nothing <- receiveTimeout 1000000 [match (\(Add _ _ _) -> return ())]
455+ res <- receiveTimeout 1000000 [match (\Add{} -> return ())]
404− liftIO $ putMVar done ()
456+ liftIO $ putMVar done $ res == Nothing
405
457
406− takeMVar done
458+ verifyClient "Expected receiveTimeout to timeout..." done
407
459
408-- | Test zero timeout
460-- | Test zero timeout
409testTimeout0 :: TestTransport -> Assertion
461testTimeout0 :: TestTransport -> Assertion
410testTimeout0 TestTransport{..} = do
462testTimeout0 TestTransport{..} = do
411 serverAddr <- newEmptyMVar
463 serverAddr <- newEmptyMVar
412 clientDone <- newEmptyMVar
464 clientDone <- newEmptyMVar
413− messagesSent <- newEmptyMVar
414
465
415 forkIO $ do
466 forkIO $ do
416 localNode <- newLocalNode testTransport initRemoteTable
467 localNode <- newLocalNode testTransport initRemoteTable
417 addr <- forkProcess localNode $ do
468 addr <- forkProcess localNode $ do
418− liftIO $ readMVar messagesSent >> threadDelay 1000000
419 -- Variation on the venerable ping server which uses a zero timeout
469 -- Variation on the venerable ping server which uses a zero timeout
420− -- Since we wait for all messages to be sent before doing this receive,
470+ partner <- fix $ \loop ->
421− -- we should nevertheless find the right message immediately
422− Just partner <- receiveTimeout 0 [match (\(Pong partner) -> return partner)]
471+ receiveTimeout 0 [match (\(Pong partner) -> return partner)]
472+ >>= maybe (liftIO (threadDelay 100000) >> loop) return
423 self <- getSelfPid
473 self <- getSelfPid
424 send partner (Ping self)
474 send partner (Ping self)
425 putMVar serverAddr addr
475 putMVar serverAddr addr
@@ -433,11 +483,9 @@ testTimeout0 TestTransport{..} = do
433 -- is not interested in, and then a single message that it wants
483 -- is not interested in, and then a single message that it wants
434 replicateM_ 10000 $ send server "Irrelevant message"
484 replicateM_ 10000 $ send server "Irrelevant message"
435 send server (Pong pid)
485 send server (Pong pid)
436− liftIO $ putMVar messagesSent ()
437− Ping _ <- expect
438− liftIO $ putMVar clientDone ()
486+ expectPing clientDone
439
487
440− takeMVar clientDone
488+ verifyClient "Expected Ping from server" clientDone
441
489
442-- | Test typed channels
490-- | Test typed channels
443testTypedChannels :: TestTransport -> Assertion
491testTypedChannels :: TestTransport -> Assertion
@@ -460,10 +508,10 @@ testTypedChannels TestTransport{..} = do
460 runProcess localNode $ do
508 runProcess localNode $ do
461 (clientSendPort, rport) <- newChan
509 (clientSendPort, rport) <- newChan
462 sendChan serverSendPort (clientSendPort, 5)
510 sendChan serverSendPort (clientSendPort, 5)
463− False <- receiveChan rport
511+ ch <- receiveChan rport
464− liftIO $ putMVar clientDone ()
512+ liftIO $ putMVar clientDone $ ch == False
465
513
466− takeMVar clientDone
514+ verifyClient "Expected channel to send 'False'" clientDone
467
515
468-- | Test merging receive ports
516-- | Test merging receive ports
469testMergeChannels :: TestTransport -> Assertion
517testMergeChannels :: TestTransport -> Assertion
@@ -486,9 +534,8 @@ testMergeChannels TestTransport{..} = do
486 rs <- mapM charChannel "abc"
534 rs <- mapM charChannel "abc"
487 m <- mergePorts biased rs
535 m <- mergePorts biased rs
488 xs <- replicateM 9 $ receiveChan m
536 xs <- replicateM 9 $ receiveChan m
489− True <- return $ xs == expected
490− liftIO $ putMVar done ()
537+ liftIO $ putMVar done $ xs == expected
491− takeMVar done
538+ verifyClient "Expected single layer merge to match expected ordering" done
492
539
493 -- Two layers of merging
540 -- Two layers of merging
494 testNested :: LocalNode -> Bool -> Bool -> String -> IO ()
541 testNested :: LocalNode -> Bool -> Bool -> String -> IO ()
@@ -499,9 +546,8 @@ testMergeChannels TestTransport{..} = do
499 ms <- mapM (mergePorts biasedInner) rss
546 ms <- mapM (mergePorts biasedInner) rss
500 m <- mergePorts biasedOuter ms
547 m <- mergePorts biasedOuter ms
501 xs <- replicateM (9 * 3) $ receiveChan m
548 xs <- replicateM (9 * 3) $ receiveChan m
502− True <- return $ xs == expected
549+ liftIO $ putMVar done $ xs == expected
503− liftIO $ putMVar done ()
550+ verifyClient "Expected nested channels to match expeted ordering" done
504− takeMVar done
505
551
506 -- Test that if no messages are (immediately) available, the scheduler makes no difference
552 -- Test that if no messages are (immediately) available, the scheduler makes no difference
507 testBlocked :: LocalNode -> Bool -> IO ()
553 testBlocked :: LocalNode -> Bool -> IO ()
@@ -510,43 +556,45 @@ testMergeChannels TestTransport{..} = do
510 done <- newEmptyMVar
556 done <- newEmptyMVar
511
557
512 forkProcess localNode $ do
558 forkProcess localNode $ do
513− [sa, sb, sc] <- liftIO $ mapM readMVar vs
559+ ss <- liftIO $ mapM readMVar vs
560+ case ss of
561+ [sa, sb, sc] ->
514− mapM_ ((>> liftIO (threadDelay 10000)) . uncurry sendChan)
562+ mapM_ ((>> pause 10000) . uncurry sendChan)
515− [ -- a, b, c
563+ [ -- a, b, c
516− (sa, 'a')
564+ (sa, 'a')
517− , (sb, 'b')
565+ , (sb, 'b')
518− , (sc, 'c')
566+ , (sc, 'c')
519− -- a, c, b
567+ -- a, c, b
520− , (sa, 'a')
568+ , (sa, 'a')
521− , (sc, 'c')
569+ , (sc, 'c')
522− , (sb, 'b')
570+ , (sb, 'b')
523− -- b, a, c
571+ -- b, a, c
524− , (sb, 'b')
572+ , (sb, 'b')
525− , (sa, 'a')
573+ , (sa, 'a')
526− , (sc, 'c')
574+ , (sc, 'c')
527− -- b, c, a
575+ -- b, c, a
528− , (sb, 'b')
576+ , (sb, 'b')
529− , (sc, 'c')
577+ , (sc, 'c')
530− , (sa, 'a')
578+ , (sa, 'a')
531− -- c, a, b
579+ -- c, a, b
532− , (sc, 'c')
580+ , (sc, 'c')
533− , (sa, 'a')
581+ , (sa, 'a')
534− , (sb, 'b')
582+ , (sb, 'b')
535− -- c, b, a
583+ -- c, b, a
536− , (sc, 'c')
584+ , (sc, 'c')
537− , (sb, 'b')
585+ , (sb, 'b')
538− , (sa, 'a')
586+ , (sa, 'a')
539− ]
587+ ]
588+ _ -> die "Something went horribly wrong"
540
589
541 forkProcess localNode $ do
590 forkProcess localNode $ do
542 (ss, rs) <- unzip <$> replicateM 3 newChan
591 (ss, rs) <- unzip <$> replicateM 3 newChan
543 liftIO $ mapM_ (uncurry putMVar) $ zip vs ss
592 liftIO $ mapM_ (uncurry putMVar) $ zip vs ss
544 m <- mergePorts biased rs
593 m <- mergePorts biased rs
545 xs <- replicateM (6 * 3) $ receiveChan m
594 xs <- replicateM (6 * 3) $ receiveChan m
546− True <- return $ xs == "abcacbbacbcacabcba"
547− liftIO $ putMVar done ()
595+ liftIO $ putMVar done $ xs == "abcacbbacbcacabcba"
548
596
549− takeMVar done
597+ verifyClient "Expected merged ports to match expected ordering" done
550
598
551 mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)
599 mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)
552 mergePorts True = mergePortsBiased
600 mergePorts True = mergePortsBiased
@@ -562,19 +610,11 @@ testMergeChannels TestTransport{..} = do
562testTerminate :: TestTransport -> Assertion
610testTerminate :: TestTransport -> Assertion
563testTerminate TestTransport{..} = do
611testTerminate TestTransport{..} = do
564 localNode <- newLocalNode testTransport initRemoteTable
612 localNode <- newLocalNode testTransport initRemoteTable
565− done <- newEmptyMVar
566−
567− pid <- forkProcess localNode $ do
568− liftIO $ threadDelay 100000
569− terminate
570−
571 runProcess localNode $ do
613 runProcess localNode $ do
572− ref <- monitor pid
614+ e <- try terminate :: Process (Either ProcessTerminationException ())
573− ProcessMonitorNotification ref' pid' (DiedException ex) <- expect
615+ if either show show e == show ProcessTerminationException
574− True <- return $ ref == ref' && pid == pid' && ex == show ProcessTerminationException
616+ then return ()
575− liftIO $ putMVar done ()
617+ else die "Unexpected result from terminate"
576−
577− takeMVar done
578
618
579testMonitorNode :: TestTransport -> Assertion
619testMonitorNode :: TestTransport -> Assertion
580testMonitorNode TestTransport{..} = do
620testMonitorNode TestTransport{..} = do
@@ -585,11 +625,12 @@ testMonitorNode TestTransport{..} = do
585
625
586 runProcess node2 $ do
626 runProcess node2 $ do
587 ref <- monitorNode (localNodeId node1)
627 ref <- monitorNode (localNodeId node1)
628+ receiveWait [
588− NodeMonitorNotification ref' nid DiedDisconnect <- expect
629+ match (\(NodeMonitorNotification ref' nid DiedDisconnect) ->
589− True <- return $ ref == ref' && nid == localNodeId node1
630+ return $ ref == ref' && nid == localNodeId node1)
590− liftIO $ putMVar done ()
631+ ] >>= liftIO . putMVar done
591
632
592− takeMVar done
633+ verifyClient "Expected NodeMonitorNotification with matching ref & nodeId" done
593
634
594testMonitorLiveNode :: TestTransport -> Assertion
635testMonitorLiveNode :: TestTransport -> Assertion
595testMonitorLiveNode TestTransport{..} = do
636testMonitorLiveNode TestTransport{..} = do
@@ -604,15 +645,16 @@ testMonitorLiveNode TestTransport{..} = do
604 liftIO $ putMVar ready ()
645 liftIO $ putMVar ready ()
605 liftIO $ takeMVar readyr
646 liftIO $ takeMVar readyr
606 send p ()
647 send p ()
648+ receiveWait [
607− NodeMonitorNotification ref' nid _ <- expect
649+ match (\(NodeMonitorNotification ref' nid _) ->
608− True <- return $ ref == ref' && nid == localNodeId node1
650+ (return $ ref == ref' && nid == localNodeId node1))
609− liftIO $ putMVar done ()
651+ ] >>= liftIO . putMVar done
610
652
611 takeMVar ready
653 takeMVar ready
612 closeLocalNode node1
654 closeLocalNode node1
613 putMVar readyr ()
655 putMVar readyr ()
614
656
615− takeMVar done
657+ verifyClient "Expected NodeMonitorNotification for LIVE node" done
616
658
617testMonitorChannel :: TestTransport -> Assertion
659testMonitorChannel :: TestTransport -> Assertion
618testMonitorChannel TestTransport{..} = do
660testMonitorChannel TestTransport{..} = do
@@ -622,18 +664,20 @@ testMonitorChannel TestTransport{..} = do
622 pid <- forkProcess node1 $ do
664 pid <- forkProcess node1 $ do
623 sport <- expect :: Process (SendPort ())
665 sport <- expect :: Process (SendPort ())
624 ref <- monitorPort sport
666 ref <- monitorPort sport
625− PortMonitorNotification ref' port' reason <- expect
667+ receiveWait [
626− -- reason might be DiedUnknownId if the receive port is GCed before the
668+ -- reason might be DiedUnknownId if the receive port is GCed before the
627− -- monitor is established (TODO: not sure that this is reasonable)
669+ -- monitor is established (TODO: not sure that this is reasonable)
670+ match (\(PortMonitorNotification ref' port' reason) ->
671+ return $ ref' == ref && port' == sendPortId sport &&
628− return $ ref' == ref && port' == sendPortId sport && (reason == DiedNormal || reason == DiedUnknownId)
672+ (reason == DiedNormal || reason == DiedUnknownId))
629− liftIO $ putMVar gotNotification ()
673+ ] >>= liftIO . putMVar gotNotification
630
674
631 runProcess node2 $ do
675 runProcess node2 $ do
632 (sport, _) <- newChan :: Process (SendPort (), ReceivePort ())
676 (sport, _) <- newChan :: Process (SendPort (), ReceivePort ())
633 send pid sport
677 send pid sport
634 liftIO $ threadDelay 100000
678 liftIO $ threadDelay 100000
635
679
636− takeMVar gotNotification
680+ verifyClient "Expected PortMonitorNotification" gotNotification
637
681
638testRegistry :: TestTransport -> Assertion
682testRegistry :: TestTransport -> Assertion
639testRegistry TestTransport{..} = do
683testRegistry TestTransport{..} = do
@@ -645,20 +689,28 @@ testRegistry TestTransport{..} = do
645
689
646 runProcess node $ do
690 runProcess node $ do
647 register "ping" pingServer
691 register "ping" pingServer
648− Just pid <- whereis "ping"
692+ whereis "ping" >>= liftIO . assertBool "Unexpected ping" . (== Just pingServer)
649− True <- return $ pingServer == pid
650 us <- getSelfPid
693 us <- getSelfPid
651 nsend "ping" (Pong us)
694 nsend "ping" (Pong us)
652− Ping pid' <- expect
695+ receiveWait [
653− True <- return $ pingServer == pid'
696+ matchIf (\(Ping pid') -> pingServer == pid') (const $ return ())
654− Left (ProcessRegistrationException "dead" Nothing) <- try $ register "dead" deadProcess
697+ ]
655− Left (ProcessRegistrationException "ping" (Just x)) <- try $ register "ping" deadProcess
698+ checkRegException "dead" Nothing deadProcess
656− True <- return $ x == pingServer
699+ checkRegException "ping" (Just pingServer) deadProcess
657− Left (ProcessRegistrationException "dead" Nothing) <- try $ unregister "dead"
700+ try (unregister "dead") >>= checkReg "dead" Nothing
658 liftIO $ putMVar done ()
701 liftIO $ putMVar done ()
659
702
660 takeMVar done
703 takeMVar done
661
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+
662testRegistryRemoteProcess :: TestTransport -> Assertion
714testRegistryRemoteProcess :: TestTransport -> Assertion
663testRegistryRemoteProcess TestTransport{..} = do
715testRegistryRemoteProcess TestTransport{..} = do
664 node1 <- newLocalNode testTransport initRemoteTable
716 node1 <- newLocalNode testTransport initRemoteTable
@@ -669,13 +721,13 @@ testRegistryRemoteProcess TestTransport{..} = do
669
721
670 runProcess node2 $ do
722 runProcess node2 $ do
671 register "ping" pingServer
723 register "ping" pingServer
672− Just pid <- whereis "ping"
724+ whereis "ping" >>= liftIO . assertBool "Unexpected ping" . (== Just pingServer)
673− True <- return $ pingServer == pid
674 us <- getSelfPid
725 us <- getSelfPid
675 nsend "ping" (Pong us)
726 nsend "ping" (Pong us)
676− Ping pid' <- expect
727+ receiveWait [
677− True <- return $ pingServer == pid'
728+ matchIf (\(Ping pid') -> pingServer == pid')
678− liftIO $ putMVar done ()
729+ (const $ liftIO $ putMVar done ())
730+ ]
679
731
680 takeMVar done
732 takeMVar done
681
733
@@ -683,7 +735,6 @@ testRemoteRegistry :: TestTransport -> Assertion
683testRemoteRegistry TestTransport{..} = do
735testRemoteRegistry TestTransport{..} = do
684 node1 <- newLocalNode testTransport initRemoteTable
736 node1 <- newLocalNode testTransport initRemoteTable
685 node2 <- newLocalNode testTransport initRemoteTable
737 node2 <- newLocalNode testTransport initRemoteTable
686− done <- newEmptyMVar
687
738
688 pingServer <- forkProcess node1 ping
739 pingServer <- forkProcess node1 ping
689 deadProcess <- forkProcess node1 (return ())
740 deadProcess <- forkProcess node1 (return ())
@@ -696,32 +747,34 @@ testRemoteRegistry TestTransport{..} = do
696 "ping" == label' && pid == pingServer)
747 "ping" == label' && pid == pingServer)
697 (\(RegisterReply _ _ _) -> return ()) ]
748 (\(RegisterReply _ _ _) -> return ()) ]
698
749
699− Just pid <- whereisRemote nid1 "ping"
750+ pid <- verifyWhereIsRemote nid1 "ping"
700− True <- return $ pingServer == pid
751+ liftIO $ assertBool "Expected pindServer to match pid" $ pingServer == pid
752+
701 us <- getSelfPid
753 us <- getSelfPid
702 nsendRemote nid1 "ping" (Pong us)
754 nsendRemote nid1 "ping" (Pong us)
703− Ping pid' <- expect
755+ receiveWait [
704− True <- return $ pingServer == pid'
756+ match (\(Ping pid') -> return $ pingServer == pid')
757+ ] >>= liftIO . assertBool "Expected Ping with ping server's ProcessId"
705
758
706 -- test that if process was not registered Nothing is returned
759 -- test that if process was not registered Nothing is returned
707 -- in owner field.
760 -- in owner field.
708 registerRemoteAsync nid1 "dead" deadProcess
761 registerRemoteAsync nid1 "dead" deadProcess
709− receiveWait [
710− matchIf (\(RegisterReply label' False Nothing) -> "dead" == label')
762+ receiveWait [ matchIf (\(RegisterReply label' f mPid) -> "dead" == label')
711− (\(RegisterReply _ _ _) -> return ()) ]
763+ (\(RegisterReply _ f mPid) -> return (not f && isNothing mPid))
764+ ] >>= liftIO . assertBool "Expected False Nothing in RegisterReply"
765+
712 registerRemoteAsync nid1 "ping" deadProcess
766 registerRemoteAsync nid1 "ping" deadProcess
713 receiveWait [
767 receiveWait [
714− matchIf (\(RegisterReply label' False (Just pid)) ->
768+ matchIf (\(RegisterReply label' False mPid) ->
715− "ping" == label' && pid == pingServer)
769+ "ping" == label' && isJust mPid)
716− (\(RegisterReply _ _ _) -> return ()) ]
770+ (\(RegisterReply _ f (Just pid'')) -> return (not f && pid'' == pingServer))
771+ ] >>= liftIO . assertBool "Expected False and (Just alreadyRegisteredPid) in RegisterReply"
772+
717 unregisterRemoteAsync nid1 "dead"
773 unregisterRemoteAsync nid1 "dead"
718 receiveWait [
774 receiveWait [
719− matchIf (\(RegisterReply label' False Nothing) ->
775+ matchIf (\(RegisterReply label' _ _) -> "dead" == label')
720− "dead" == label' && pid == pingServer)
776+ (\(RegisterReply _ f mPid) -> return (not f && isNothing mPid))
721− (\(RegisterReply _ _ _) -> return ()) ]
777+ ] >>= liftIO . assertBool "Expected False and Nothing in RegisterReply"
722− liftIO $ putMVar done ()
723−
724− takeMVar done
725
778
726testRemoteRegistryRemoteProcess :: TestTransport -> Assertion
779testRemoteRegistryRemoteProcess :: TestTransport -> Assertion
727testRemoteRegistryRemoteProcess TestTransport{..} = do
780testRemoteRegistryRemoteProcess TestTransport{..} = do
@@ -737,15 +790,15 @@ testRemoteRegistryRemoteProcess TestTransport{..} = do
737 receiveWait [
790 receiveWait [
738 matchIf (\(RegisterReply label' _ _) -> "ping" == label')
791 matchIf (\(RegisterReply label' _ _) -> "ping" == label')
739 (\(RegisterReply _ _ _) -> return ()) ]
792 (\(RegisterReply _ _ _) -> return ()) ]
740− Just pid <- whereisRemote nid1 "ping"
793+ pid <- verifyWhereIsRemote nid1 "ping"
741− True <- return $ pingServer == pid
794+ liftIO $ assertBool "Expected pingServer to match remote name" $ pingServer == pid
742 us <- getSelfPid
795 us <- getSelfPid
743 nsendRemote nid1 "ping" (Pong us)
796 nsendRemote nid1 "ping" (Pong us)
744− Ping pid' <- expect
797+ receiveWait [
745− True <- return $ pingServer == pid'
798+ match (\(Ping pid') -> return $ pingServer == pid')
746− liftIO $ putMVar done ()
799+ ] >>= liftIO . putMVar done
747
800
748− takeMVar done
801+ verifyClient "Expected Ping with ping server's ProcessId" done
749
802
750testSpawnLocal :: TestTransport -> Assertion
803testSpawnLocal :: TestTransport -> Assertion
751testSpawnLocal TestTransport{..} = do
804testSpawnLocal TestTransport{..} = do
@@ -759,15 +812,14 @@ testSpawnLocal TestTransport{..} = do
759 sport <- expect
812 sport <- expect
760 sendChan sport (1234 :: Int)
813 sendChan sport (1234 :: Int)
761
814
762− sport <- spawnChannelLocal $ \rport -> do
815+ sport <- spawnChannelLocal $
763− (1234 :: Int) <- receiveChan rport
816+ \rport -> (receiveChan rport :: Process Int) >>= send us
764− send us ()
765
817
766 send pid sport
818 send pid sport
767− () <- expect
768− liftIO $ putMVar done ()
819+ expect >>= liftIO . putMVar done
769
820
770− takeMVar done
821+ res <- takeMVar done
822+ assertBool "Expected 1234 :: Int" $ res == (1234 :: Int)
771
823
772testSpawnAsyncStrictness :: TestTransport -> Assertion
824testSpawnAsyncStrictness :: TestTransport -> Assertion
773testSpawnAsyncStrictness TestTransport{..} = do
825testSpawnAsyncStrictness TestTransport{..} = do
@@ -796,8 +848,9 @@ testReconnect TestTransport{..} = do
796 liftIO $ putMVar processA us
848 liftIO $ putMVar processA us
797 msg1 <- expect
849 msg1 <- expect
798 msg2 <- expect
850 msg2 <- expect
851+ liftIO $ do
799− True <- return $ msg1 == "message 1" && msg2 == "message 3"
852+ assertBool "messages did not match" $ msg1 == "message 1" && msg2 == "message 3"
800− liftIO $ putMVar sendTestOk ()
853+ putMVar sendTestOk ()
801
854
802 forkProcess node2 $ do
855 forkProcess node2 $ do
803 {-
856 {-
@@ -830,7 +883,7 @@ testReconnect TestTransport{..} = do
830 matchIf (\(RegisterReply label' _ _) -> "a" == label')
883 matchIf (\(RegisterReply label' _ _) -> "a" == label')
831 (\(RegisterReply _ _ _) -> return ()) ]
884 (\(RegisterReply _ _ _) -> return ()) ]
832
885
833− Just _ <- whereisRemote nid1 "a"
886+ _ <- whereisRemote nid1 "a"
834
887
835
888
836 -- Simulate network failure
889 -- Simulate network failure
@@ -849,9 +902,10 @@ testReconnect TestTransport{..} = do
849 (\(RegisterReply _ _ _) -> return ()) ]
902 (\(RegisterReply _ _ _) -> return ()) ]
850
903
851 -- Check
904 -- Check
852− Nothing <- whereisRemote nid1 "a" -- this will fail because the name is removed when the node is disconnected
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
853− Just _ <- whereisRemote nid1 "b" -- this will suceed because the value is set after thereconnect
907+ verifyWhereIsRemote nid1 "b" -- this will suceed because the value is set after thereconnect
854− Just _ <- whereisRemote nid1 "c"
908+ verifyWhereIsRemote nid1 "c"
855
909
856 liftIO $ putMVar registerTestOk ()
910 liftIO $ putMVar registerTestOk ()
857
911
@@ -892,19 +946,19 @@ testUSend usendPrim TestTransport{..} numMessages = do
892 isSorted _ = True
946 isSorted _ = True
893 -- The list can't be null since there are no failures after sending
947 -- The list can't be null since there are no failures after sending
894 -- the last message.
948 -- the last message.
895− True <- return $ isSorted msgs && not (null msgs)
949+ liftIO $ putMVar usendTestOk $ isSorted msgs && not (null msgs)
896− liftIO $ putMVar usendTestOk ()
897
950
898 forkProcess node2 $ do
951 forkProcess node2 $ do
899 them <- liftIO $ readMVar processA
952 them <- liftIO $ readMVar processA
900 getSelfPid >>= send them
953 getSelfPid >>= send them
901− () <- expect
954+ expect :: Process ()
902 forM_ [1..numMessages] $ \i -> do
955 forM_ [1..numMessages] $ \i -> do
903 liftIO $ testBreakConnection (nodeAddress nid1) (nodeAddress nid2)
956 liftIO $ testBreakConnection (nodeAddress nid1) (nodeAddress nid2)
904 usendPrim them i
957 usendPrim them i
905 liftIO (threadDelay 30000)
958 liftIO (threadDelay 30000)
906
959
907− takeMVar usendTestOk
960+ res <- takeMVar usendTestOk
961+ assertBool "Unexpected failure after sending last msg" res
908
962
909-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
963-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
910-- in between
964-- in between
@@ -930,14 +984,17 @@ testMatchAny TestTransport{..} = do
930 runProcess localNode $ do
984 runProcess localNode $ do
931 pid <- getSelfPid
985 pid <- getSelfPid
932 send mathServer (Add pid 1 2)
986 send mathServer (Add pid 1 2)
933− 3 <- expect :: Process Double
987+ three <- expect :: Process Double
934 send mathServer (Divide pid 8 2)
988 send mathServer (Divide pid 8 2)
935− 4 <- expect :: Process Double
989+ four <- expect :: Process Double
936 send mathServer (Divide pid 8 0)
990 send mathServer (Divide pid 8 0)
937− DivByZero <- expect
991+ divByZ <- expect
938− liftIO $ putMVar clientDone ()
992+ liftIO $ putMVar clientDone (three, four, divByZ)
939
993
940− takeMVar clientDone
994+ res <- takeMVar clientDone
995+ case res of
996+ (3, 4, DivByZero) -> return ()
997+ _ -> error "Unexpected result"
941
998
942-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
999-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
943-- in between, however we block 'Divide' requests ....
1000-- in between, however we block 'Divide' requests ....
@@ -964,12 +1021,13 @@ testMatchAnyHandle TestTransport{..} = do
964 runProcess localNode $ do
1021 runProcess localNode $ do
965 pid <- getSelfPid
1022 pid <- getSelfPid
966 send mathServer (Add pid 1 2)
1023 send mathServer (Add pid 1 2)
967− 3 <- expect :: Process Double
1024+ three <- expect :: Process Double
968 send mathServer (Divide pid 8 2)
1025 send mathServer (Divide pid 8 2)
969− Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
1026+ res <- (expectTimeout 100000) :: Process (Maybe Double)
970− liftIO $ putMVar clientDone ()
1027+ liftIO $ putMVar clientDone $ three == 3 && res == Nothing
1028+
1029+ verifyClient "Expected Nothing (i.e. timeout)" clientDone
971
1030
972− takeMVar clientDone
973 where maybeForward :: ProcessId -> Message -> Process (Maybe ())
1031 where maybeForward :: ProcessId -> Message -> Process (Maybe ())
974 maybeForward s msg =
1032 maybeForward s msg =
975 handleMessage msg (\m@(Add _ _ _) -> send s m)
1033 handleMessage msg (\m@(Add _ _ _) -> send s m)
@@ -997,8 +1055,10 @@ testMatchAnyNoHandle TestTransport{..} = do
997 Just _ -> die "NONSENSE")
1055 Just _ -> die "NONSENSE")
998 ]
1056 ]
999 -- we *must* have removed the message from our mailbox though!!!
1057 -- we *must* have removed the message from our mailbox though!!!
1000− Nothing <- receiveTimeout 100000 [ match (\(Add _ _ _) -> return ()) ]
1058+ res <- receiveTimeout 100000 [ match (\(Add _ _ _) -> return ()) ]
1059+ liftIO $ do
1060+ assertBool "Expected timeout!" $ res == Nothing
1001− liftIO $ putMVar serverDone ()
1061+ putMVar serverDone ()
1002 putMVar addr server
1062 putMVar addr server
1003
1063
1004 -- Client
1064 -- Client
@@ -1041,14 +1101,21 @@ testMatchAnyIf TestTransport{..} = do
1041 runProcess localNode $ do
1101 runProcess localNode $ do
1042 pid <- getSelfPid
1102 pid <- getSelfPid
1043 send server (pid, "foo")
1103 send server (pid, "foo")
1044− "foo" <- expect
1104+ foo <- expect
1045− send server (pid, "baz")
1105+ -- provoking what would be the wrong ordering is informative here...
1046− "baz" <- expect
1106+
1047 send server (pid, "bar")
1107 send server (pid, "bar")
1048− Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
1108+ bar <- (expectTimeout 100000) :: Process (Maybe String) -- was Double o_O !?
1109+
1110+ send server (pid, "baz")
1111+ baz <- expect
1112+
1049− liftIO $ putMVar clientDone ()
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'
1050
1118
1051− takeMVar clientDone
1052 where tryHandleMessage :: Message -> Process (Maybe ())
1119 where tryHandleMessage :: Message -> Process (Maybe ())
1053 tryHandleMessage msg =
1120 tryHandleMessage msg =
1054 handleMessage msg (\(pid :: ProcessId, (m :: String))
1121 handleMessage msg (\(pid :: ProcessId, (m :: String))
@@ -1081,17 +1148,19 @@ testMatchMessageWithUnwrap TestTransport{..} = do
1081 runProcess localNode $ do
1148 runProcess localNode $ do
1082 pid <- getSelfPid
1149 pid <- getSelfPid
1083 send server (pid, wrapMessage ("foo" :: String))
1150 send server (pid, wrapMessage ("foo" :: String))
1084− "foo" <- expect
1151+ foo <- expect
1085 send server (pid, wrapMessage ("baz" :: String))
1152 send server (pid, wrapMessage ("baz" :: String))
1086− "baz" <- expect
1153+ baz <- expect
1087− liftIO $ putMVar clientDone ()
1154+ liftIO $ putMVar clientDone (foo, baz)
1088
1155
1089− takeMVar clientDone
1156+ res <- takeMVar clientDone
1157+ assertBool "Unexpected unwrapped results" $ res == ("foo", "baz")
1090
1158
1091-- Test 'receiveChanTimeout'
1159-- Test 'receiveChanTimeout'
1092testReceiveChanTimeout :: TestTransport -> Assertion
1160testReceiveChanTimeout :: TestTransport -> Assertion
1093testReceiveChanTimeout TestTransport{..} = do
1161testReceiveChanTimeout TestTransport{..} = do
1094 done <- newEmptyMVar
1162 done <- newEmptyMVar
1163+ mvSender <- newEmptyMVar
1095 sendPort <- newEmptyMVar
1164 sendPort <- newEmptyMVar
1096
1165
1097 forkTry $ do
1166 forkTry $ do
@@ -1101,18 +1170,32 @@ testReceiveChanTimeout TestTransport{..} = do
1101 (sp, rp) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
1170 (sp, rp) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
1102 liftIO $ putMVar sendPort sp
1171 liftIO $ putMVar sendPort sp
1103
1172
1104− -- Wait for a message with a delay. No message arrives, we should get Nothing after 1 second
1173+ -- Wait for a message with a delay. No message arrives, we should get
1174+ -- Nothing after the delay.
1105− Nothing <- receiveChanTimeout 1000000 rp
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 ()
1106
1179
1107− -- Wait for a message with a delay again. Now a message arrives after 0.5 seconds
1180+ -- Wait for a message with a delay again. Now a message arrives after
1181+ -- 0.1 seconds
1108− Just True <- receiveChanTimeout 1000000 rp
1182+ res <- receiveChanTimeout 20000000 rp >>= maybe (die "Timeout") return
1183+ liftIO $ assertBool "Expected result to be 'True'" res
1109
1184
1110− -- Wait for a message with zero timeout: non-blocking check. No message is available, we get Nothing
1185+ -- Wait for a message with zero timeout: non-blocking check. No message is
1186+ -- available, we get Nothing
1111− Nothing <- receiveChanTimeout 0 rp
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 ()
1112
1191
1113 -- Again, but now there is a message available
1192 -- Again, but now there is a message available
1193+ fix $ \loop -> do
1114− liftIO $ threadDelay 1000000
1194+ liftIO $ threadDelay 100000
1115− Just False <- receiveChanTimeout 0 rp
1195+ mb <- receiveChanTimeout 0 rp
1196+ case mb of
1197+ Just b -> do liftIO $ assertBool "Unexpected Message" $ not b
1198+ _ -> loop
1116
1199
1117 liftIO $ putMVar done ()
1200 liftIO $ putMVar done ()
1118
1201
@@ -1121,10 +1204,11 @@ testReceiveChanTimeout TestTransport{..} = do
1121 runProcess localNode $ do
1204 runProcess localNode $ do
1122 sp <- liftIO $ readMVar sendPort
1205 sp <- liftIO $ readMVar sendPort
1123
1206
1207+ liftIO $ takeMVar mvSender
1124− liftIO $ threadDelay 1500000
1208+ liftIO $ threadDelay 100000
1125 sendChan sp True
1209 sendChan sp True
1126
1210
1127− liftIO $ threadDelay 500000
1211+ liftIO $ takeMVar mvSender
1128 sendChan sp False
1212 sendChan sp False
1129
1213
1130 takeMVar done
1214 takeMVar done
@@ -1147,8 +1231,8 @@ testReceiveChanFeatures TestTransport{..} = do
1147
1231
1148 rp1 <- mergePortsBiased [even <$> rpInt, rpBool]
1232 rp1 <- mergePortsBiased [even <$> rpInt, rpBool]
1149
1233
1150− True <- receiveChan rp1
1234+ receiveChan rp1 >>= liftIO . assertBool "Expected True"
1151− False <- receiveChan rp1
1235+ receiveChan rp1 >>= liftIO . assertBool "Expected False" . not
1152
1236
1153 -- Test Applicative instance
1237 -- Test Applicative instance
1154
1238
@@ -1157,7 +1241,7 @@ testReceiveChanFeatures TestTransport{..} = do
1157
1241
1158 let rp2 = pure (+) <*> rpInt <*> rpInt
1242 let rp2 = pure (+) <*> rpInt <*> rpInt
1159
1243
1160− 7 <- receiveChan rp2
1244+ receiveChan rp2 >>= liftIO . assertBool "Expected 7" . (== 7)
1161
1245
1162 -- Test Alternative instance
1246 -- Test Alternative instance
1163
1247
@@ -1166,8 +1250,8 @@ testReceiveChanFeatures TestTransport{..} = do
1166
1250
1167 let rp3 = (even <$> rpInt) <|> rpBool
1251 let rp3 = (even <$> rpInt) <|> rpBool
1168
1252
1169− False <- receiveChan rp3
1253+ receiveChan rp3 >>= liftIO . assertBool "Expected False" . not
1170− True <- receiveChan rp3
1254+ receiveChan rp3 >>= liftIO . assertBool "Expected True"
1171
1255
1172 -- Test Monad instance
1256 -- Test Monad instance
1173
1257
@@ -1181,17 +1265,86 @@ testReceiveChanFeatures TestTransport{..} = do
1181 then rpInt
1265 then rpInt
1182 else return 7
1266 else return 7
1183
1267
1184− 5 <- receiveChan rp4
1268+ receiveChan rp4 >>= liftIO . assertBool "Expected 5" . (== 5)
1185− 7 <- receiveChan rp4
1269+ receiveChan rp4 >>= liftIO . assertBool "Expected 7" . (== 7)
1186
1270
1187 liftIO $ putMVar done ()
1271 liftIO $ putMVar done ()
1188
1272
1189 takeMVar done
1273 takeMVar done
1190
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+
1191testKillLocal :: TestTransport -> Assertion
1345testKillLocal :: TestTransport -> Assertion
1192testKillLocal TestTransport{..} = do
1346testKillLocal TestTransport{..} = do
1193 localNode <- newLocalNode testTransport initRemoteTable
1347 localNode <- newLocalNode testTransport initRemoteTable
1194− done <- newEmptyMVar
1195
1348
1196 pid <- forkProcess localNode $ do
1349 pid <- forkProcess localNode $ do
1197 liftIO $ threadDelay 1000000
1350 liftIO $ threadDelay 1000000
@@ -1200,17 +1353,18 @@ testKillLocal TestTransport{..} = do
1200 ref <- monitor pid
1353 ref <- monitor pid
1201 us <- getSelfPid
1354 us <- getSelfPid
1202 kill pid "TestKill"
1355 kill pid "TestKill"
1203− ProcessMonitorNotification ref' pid' (DiedException ex) <- expect
1356+ mn <- expect
1357+ case mn of
1358+ ProcessMonitorNotification ref' pid' (DiedException ex) ->
1204− True <- return $ ref == ref' && pid == pid' && ex == "killed-by=" ++ show us ++ ",reason=TestKill"
1359+ case ref == ref' && pid == pid' && ex == "killed-by=" ++ show us ++ ",reason=TestKill" of
1205− liftIO $ putMVar done ()
1360+ True -> return ()
1206−
1361+ False -> die "Invalid ProcessMonitorNotification received"
1207− takeMVar done
1362+ _ -> die "Something went horribly wrong"
1208
1363
1209testKillRemote :: TestTransport -> Assertion
1364testKillRemote :: TestTransport -> Assertion
1210testKillRemote TestTransport{..} = do
1365testKillRemote TestTransport{..} = do
1211 node1 <- newLocalNode testTransport initRemoteTable
1366 node1 <- newLocalNode testTransport initRemoteTable
1212 node2 <- newLocalNode testTransport initRemoteTable
1367 node2 <- newLocalNode testTransport initRemoteTable
1213− done <- newEmptyMVar
1214
1368
1215 pid <- forkProcess node1 $ do
1369 pid <- forkProcess node1 $ do
1216 liftIO $ threadDelay 1000000
1370 liftIO $ threadDelay 1000000
@@ -1219,11 +1373,17 @@ testKillRemote TestTransport{..} = do
1219 ref <- monitor pid
1373 ref <- monitor pid
1220 us <- getSelfPid
1374 us <- getSelfPid
1221 kill pid "TestKill"
1375 kill pid "TestKill"
1376+ mn <- expect
1377+ case mn of
1222− ProcessMonitorNotification ref' pid' (DiedException reason) <- expect
1378+ ProcessMonitorNotification ref' pid' (DiedException reason) ->
1223− True <- return $ ref == ref' && pid == pid' && reason == "killed-by=" ++ show us ++ ",reason=TestKill"
1379+ case (ref == ref', pid == pid', reason == "killed-by=" ++ show us ++ ",reason=TestKill") of
1224− liftIO $ putMVar done ()
1380+ (True, True, True) -> return ()
1225−
1381+ (a, b, c) -> do
1226− takeMVar done
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"
1227
1387
1228testCatchesExit :: TestTransport -> Assertion
1388testCatchesExit :: TestTransport -> Assertion
1229testCatchesExit TestTransport{..} = do
1389testCatchesExit TestTransport{..} = do
@@ -1249,7 +1409,10 @@ testHandleMessageIf TestTransport{..} = do
1249 self <- getSelfPid
1409 self <- getSelfPid
1250 send self (5 :: Integer, 10 :: Integer)
1410 send self (5 :: Integer, 10 :: Integer)
1251 msg <- receiveWait [ matchMessage return ]
1411 msg <- receiveWait [ matchMessage return ]
1252− Nothing <- handleMessageIf msg (\() -> True) (\() -> die $ "whoops")
1412+ handleMessageIf msg
1413+ (\() -> True)
1414+ (\() -> die "whoops") >>= maybe (return ())
1415+ (const $ die "Expected Mismatch")
1253 handleMessageIf msg (\(x :: Integer, y :: Integer) -> x == 5 && y == 10)
1416 handleMessageIf msg (\(x :: Integer, y :: Integer) -> x == 5 && y == 10)
1254 (\input -> liftIO $ putMVar done input)
1417 (\input -> liftIO $ putMVar done input)
1255 return ()
1418 return ()
@@ -1294,8 +1457,11 @@ testDie TestTransport{..} = do
1294 (die ("foobar", 123 :: Int))
1457 (die ("foobar", 123 :: Int))
1295 `catchExit` \_from reason -> do
1458 `catchExit` \_from reason -> do
1296 -- TODO: should verify that 'from' has the right value
1459 -- TODO: should verify that 'from' has the right value
1297− True <- return $ reason == ("foobar", 123 :: Int)
1460+ let res = reason == ("foobar", 123 :: Int)
1298 liftIO $ putMVar done ()
1461 liftIO $ putMVar done ()
1462+ if res
1463+ then return ()
1464+ else die "Something went horribly wrong"
1299
1465
1300 takeMVar done
1466 takeMVar done
1301
1467
@@ -1309,8 +1475,11 @@ testPrettyExit TestTransport{..} = do
1309 `catch` \ex@(ProcessExitException from _) ->
1475 `catch` \ex@(ProcessExitException from _) ->
1310 let expected = "exit-from=" ++ (show from)
1476 let expected = "exit-from=" ++ (show from)
1311 in do
1477 in do
1312− True <- return $ (show ex) == expected
1478+ let res = (show ex) == expected
1313 liftIO $ putMVar done ()
1479 liftIO $ putMVar done ()
1480+ if res
1481+ then return ()
1482+ else die "Something went horribly wrong"
1314
1483
1315 takeMVar done
1484 takeMVar done
1316
1485
@@ -1326,17 +1495,26 @@ testExitLocal TestTransport{..} = do
1326 pid <- forkProcess localNode $ do
1495 pid <- forkProcess localNode $ do
1327 (liftIO (putMVar handlerSetUp ()) >> expect) `catchExit` \_from reason -> do
1496 (liftIO (putMVar handlerSetUp ()) >> expect) `catchExit` \_from reason -> do
1328 -- TODO: should verify that 'from' has the right value
1497 -- TODO: should verify that 'from' has the right value
1329− True <- return $ reason == "TestExit"
1498+ res <- return $ reason == "TestExit"
1330 liftIO $ putMVar supervisedDone ()
1499 liftIO $ putMVar supervisedDone ()
1500+ if res
1501+ then return ()
1502+ else die "Something went horribly wrong"
1331
1503
1332 runProcess localNode $ do
1504 runProcess localNode $ do
1333 liftIO $ takeMVar handlerSetUp
1505 liftIO $ takeMVar handlerSetUp
1334 ref <- monitor pid
1506 ref <- monitor pid
1335 exit pid "TestExit"
1507 exit pid "TestExit"
1336 -- This time the client catches the exception, so it dies normally
1508 -- This time the client catches the exception, so it dies normally
1509+ mn <- expect
1510+ case mn of
1337− ProcessMonitorNotification ref' pid' DiedNormal <- expect
1511+ ProcessMonitorNotification ref' pid' DiedNormal -> do
1338− True <- return $ ref == ref' && pid == pid'
1512+ let res = ref == ref' && pid == pid'
1339− liftIO $ putMVar supervisorDone ()
1513+ liftIO $ putMVar supervisorDone ()
1514+ if res
1515+ then return ()
1516+ else die "Something went horribly wrong"
1517+ _ -> die "Something went horribly wrong"
1340
1518
1341 takeMVar supervisedDone
1519 takeMVar supervisedDone
1342 takeMVar supervisorDone
1520 takeMVar supervisorDone
@@ -1352,19 +1530,98 @@ testExitRemote TestTransport{..} = do
1352 (receiveWait [] :: Process ()) -- block forever
1530 (receiveWait [] :: Process ()) -- block forever
1353 `catchExit` \_from reason -> do
1531 `catchExit` \_from reason -> do
1354 -- TODO: should verify that 'from' has the right value
1532 -- TODO: should verify that 'from' has the right value
1355− True <- return $ reason == "TestExit"
1533+ res <- return $ reason == "TestExit"
1356 liftIO $ putMVar supervisedDone ()
1534 liftIO $ putMVar supervisedDone ()
1535+ if res
1536+ then return ()
1537+ else die "Something went horribly wrong"
1357
1538
1358 runProcess node2 $ do
1539 runProcess node2 $ do
1359 ref <- monitor pid
1540 ref <- monitor pid
1360 exit pid "TestExit"
1541 exit pid "TestExit"
1542+ mn <- expect
1543+ case mn of
1361− ProcessMonitorNotification ref' pid' DiedNormal <- expect
1544+ ProcessMonitorNotification ref' pid' DiedNormal -> do
1362− True <- return $ ref == ref' && pid == pid'
1545+ res' <- return $ ref == ref' && pid == pid'
1363− liftIO $ putMVar supervisorDone ()
1546+ liftIO $ putMVar supervisorDone ()
1547+ if res'
1548+ then return ()
1549+ else die "Something went horribly wrong"
1550+ _ -> die "Something went horribly wrong"
1364
1551
1365 takeMVar supervisedDone
1552 takeMVar supervisedDone
1366 takeMVar supervisorDone
1553 takeMVar supervisorDone
1367
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+
1368testUnsafeSend :: TestTransport -> Assertion
1625testUnsafeSend :: TestTransport -> Assertion
1369testUnsafeSend TestTransport{..} = do
1626testUnsafeSend TestTransport{..} = do
1370 serverAddr <- newEmptyMVar
1627 serverAddr <- newEmptyMVar
@@ -1380,8 +1637,26 @@ testUnsafeSend TestTransport{..} = do
1380 void $ forkProcess localNode $ do
1637 void $ forkProcess localNode $ do
1381 serverPid <- liftIO $ takeMVar serverAddr
1638 serverPid <- liftIO $ takeMVar serverAddr
1382 getSelfPid >>= unsafeSend serverPid
1639 getSelfPid >>= unsafeSend serverPid
1383− () <- expect
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
1384− liftIO $ putMVar clientDone ()
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
1385
1660
1386 takeMVar clientDone
1661 takeMVar clientDone
1387
1662
@@ -1392,8 +1667,7 @@ testUnsafeNSend TestTransport{..} = do
1392 localNode <- newLocalNode testTransport initRemoteTable
1667 localNode <- newLocalNode testTransport initRemoteTable
1393
1668
1394 pid <- forkProcess localNode $ do
1669 pid <- forkProcess localNode $ do
1395− () <- expect
1396− liftIO $ putMVar clientDone ()
1670+ expect >>= liftIO . putMVar clientDone
1397
1671
1398 void $ runProcess localNode $ do
1672 void $ runProcess localNode $ do
1399 register "foobar" pid
1673 register "foobar" pid
@@ -1401,6 +1675,24 @@ testUnsafeNSend TestTransport{..} = do
1401
1675
1402 takeMVar clientDone
1676 takeMVar clientDone
1403
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+
1404testUnsafeSendChan :: TestTransport -> Assertion
1696testUnsafeSendChan :: TestTransport -> Assertion
1405testUnsafeSendChan TestTransport{..} = do
1697testUnsafeSendChan TestTransport{..} = do
1406 serverAddr <- newEmptyMVar
1698 serverAddr <- newEmptyMVar
@@ -1417,7 +1709,7 @@ testUnsafeSendChan TestTransport{..} = do
1417 serverPid <- liftIO $ takeMVar serverAddr
1709 serverPid <- liftIO $ takeMVar serverAddr
1418 (sp, rp) <- newChan
1710 (sp, rp) <- newChan
1419 unsafeSend serverPid sp
1711 unsafeSend serverPid sp
1420− () <- receiveChan rp
1712+ receiveChan rp :: Process ()
1421 liftIO $ putMVar clientDone ()
1713 liftIO $ putMVar clientDone ()
1422
1714
1423 takeMVar clientDone
1715 takeMVar clientDone
@@ -1431,8 +1723,8 @@ testCallLocal TestTransport{..} = do
1431 runProcess node $ do
1723 runProcess node $ do
1432 r <- (/=) <$> getSelfPid <*> callLocal getSelfPid
1724 r <- (/=) <$> getSelfPid <*> callLocal getSelfPid
1433 liftIO $ putMVar result r
1725 liftIO $ putMVar result r
1726+
1434− True <- takeMVar result
1727+ takeMVar result >>= assertBool "Expected 'True'"
1435− return ()
1436
1728
1437 -- Testing that when callLocal is interrupted, the worker is interrupted.
1729 -- Testing that when callLocal is interrupted, the worker is interrupted.
1438 ibox <- newIORef False
1730 ibox <- newIORef False
@@ -1449,10 +1741,9 @@ testCallLocal TestTransport{..} = do
1449 (send keeper ())
1741 (send keeper ())
1450 caller <- expect
1742 caller <- expect
1451 exit caller "test"
1743 exit caller "test"
1452− () <- expect
1744+ expect :: Process ()
1453− return ()
1745+
1454− True <- readIORef ibox
1746+ readIORef ibox >>= assertBool "Expected 'True'"
1455− return ()
1456
1747
1457 -- Testing that when the worker raises an exception, the exception is propagated to the parent.
1748 -- Testing that when the worker raises an exception, the exception is propagated to the parent.
1458 ibox2 <- newIORef False
1749 ibox2 <- newIORef False
@@ -1461,8 +1752,8 @@ testCallLocal TestTransport{..} = do
1461 liftIO $ writeIORef ibox2 $ case r of
1752 liftIO $ writeIORef ibox2 $ case r of
1462 Left (ErrorCall "e") -> True
1753 Left (ErrorCall "e") -> True
1463 _ -> False
1754 _ -> False
1755+
1464− True <- readIORef ibox
1756+ readIORef ibox >>= assertBool "Expected 'True'"
1465− return ()
1466
1757
1467 -- Test that caller waits for the worker in correct situation
1758 -- Test that caller waits for the worker in correct situation
1468 ibox3 <- newIORef False
1759 ibox3 <- newIORef False
@@ -1479,8 +1770,8 @@ testCallLocal TestTransport{..} = do
1479 liftIO $ putMVar result3 =<< readIORef ibox3
1770 liftIO $ putMVar result3 =<< readIORef ibox3
1480 worker <- expect
1771 worker <- expect
1481 send worker ()
1772 send worker ()
1773+
1482− True <- takeMVar result3
1774+ takeMVar result3 >>= assertBool "Expected 'True'"
1483− return ()
1484
1775
1485 -- Test that caller waits for the worker in case when caller gets an exception
1776 -- Test that caller waits for the worker in case when caller gets an exception
1486 ibox4 <- newIORef False
1777 ibox4 <- newIORef False
@@ -1496,13 +1787,9 @@ testCallLocal TestTransport{..} = do
1496 `finally` (liftIO $ putMVar result4 =<< readIORef ibox4)
1787 `finally` (liftIO $ putMVar result4 =<< readIORef ibox4)
1497 caller <- expect
1788 caller <- expect
1498 exit caller "hi!"
1789 exit caller "hi!"
1499− True <- takeMVar result4
1500− return ()
1501− -- XXX: Testing that when mask_ $ callLocal p runs p in masked state.
1502−
1503−
1504−
1505
1790
1791+ takeMVar result4 >>= assertBool "Expected 'True'"
1792+ -- XXX: Testing that when mask_ $ callLocal p runs p in masked state.
1506
1793
1507tests :: TestTransport -> IO [Test]
1794tests :: TestTransport -> IO [Test]
1508tests testtrans = return [
1795tests testtrans = return [
@@ -1527,6 +1814,7 @@ tests testtrans = return [
1527 , testCase "MatchMessageUnwrap" (testMatchMessageWithUnwrap testtrans)
1814 , testCase "MatchMessageUnwrap" (testMatchMessageWithUnwrap testtrans)
1528 , testCase "ReceiveChanTimeout" (testReceiveChanTimeout testtrans)
1815 , testCase "ReceiveChanTimeout" (testReceiveChanTimeout testtrans)
1529 , testCase "ReceiveChanFeatures" (testReceiveChanFeatures testtrans)
1816 , testCase "ReceiveChanFeatures" (testReceiveChanFeatures testtrans)
1817+ , testCase "ChanLifecycle" (testChanLifecycle testtrans)
1530 , testCase "KillLocal" (testKillLocal testtrans)
1818 , testCase "KillLocal" (testKillLocal testtrans)
1531 , testCase "KillRemote" (testKillRemote testtrans)
1819 , testCase "KillRemote" (testKillRemote testtrans)
1532 , testCase "Die" (testDie testtrans)
1820 , testCase "Die" (testDie testtrans)
@@ -1536,10 +1824,13 @@ tests testtrans = return [
1536 , testCase "MaskRestoreScope" (testMaskRestoreScope testtrans)
1824 , testCase "MaskRestoreScope" (testMaskRestoreScope testtrans)
1537 , testCase "ExitLocal" (testExitLocal testtrans)
1825 , testCase "ExitLocal" (testExitLocal testtrans)
1538 , testCase "ExitRemote" (testExitRemote testtrans)
1826 , testCase "ExitRemote" (testExitRemote testtrans)
1827+ , testCase "RegistryMonitoring" (testRegistryMonitoring testtrans)
1539 , testCase "TextCallLocal" (testCallLocal testtrans)
1828 , testCase "TextCallLocal" (testCallLocal testtrans)
1540 -- Unsafe Primitives
1829 -- Unsafe Primitives
1541 , testCase "TestUnsafeSend" (testUnsafeSend testtrans)
1830 , testCase "TestUnsafeSend" (testUnsafeSend testtrans)
1831+ , testCase "TestUnsafeUSend" (testUnsafeUSend testtrans)
1542 , testCase "TestUnsafeNSend" (testUnsafeNSend testtrans)
1832 , testCase "TestUnsafeNSend" (testUnsafeNSend testtrans)
1833+ , testCase "TestUnsafeNSendRemote" (testUnsafeNSendRemote testtrans)
1543 , testCase "TestUnsafeSendChan" (testUnsafeSendChan testtrans)
1834 , testCase "TestUnsafeSendChan" (testUnsafeSendChan testtrans)
1544 -- usend
1835 -- usend
1545 , testCase "USend" (testUSend usend testtrans 50)
1836 , testCase "USend" (testUSend usend testtrans 50)
@@ -1586,17 +1877,3 @@ tests testtrans = return [
1586 , testCase "MonitorUnreachable" (testMonitorUnreachable testtrans True False)
1877 , testCase "MonitorUnreachable" (testMonitorUnreachable testtrans True False)
1587 ]
1878 ]
1588 ]
1879 ]
1589−
1590−syncBreakConnection :: (NT.EndPointAddress -> NT.EndPointAddress -> IO ()) -> LocalNode -> LocalNode -> IO ()
1591−syncBreakConnection breakConnection nid0 nid1 = do
1592− m <- newEmptyMVar
1593− _ <- forkProcess nid1 $ getSelfPid >>= liftIO . putMVar m
1594− runProcess nid0 $ do
1595− them <- liftIO $ takeMVar m
1596− pinger <- spawnLocal $ forever $ send them ()
1597− _ <- monitorNode (localNodeId nid1)
1598− liftIO $ breakConnection (nodeAddress $ localNodeId nid0)
1599− (nodeAddress $ localNodeId nid1)
1600− NodeMonitorNotification _ _ _ <- expect
1601− kill pinger "finished"
1602− return ()
distributed-process-tests/src/Control/Distributed/Process/Tests/Closure.hs+89−48
@@ -169,8 +169,11 @@ testUnclosure TestTransport{..} rtable = do
169 node <- newLocalNode testTransport rtable
169 node <- newLocalNode testTransport rtable
170 done <- newEmptyMVar
170 done <- newEmptyMVar
171 forkProcess node $ do
171 forkProcess node $ do
172− 120 <- join . unClosure $ factorialClosure 5
172+ i <- join . unClosure $ factorialClosure 5
173 liftIO $ putMVar done ()
173 liftIO $ putMVar done ()
174+ if i == 720
175+ then return ()
176+ else error "Something went horribly wrong"
174 takeMVar done
177 takeMVar done
175
178
176testBind :: TestTransport -> RemoteTable -> Assertion
179testBind :: TestTransport -> RemoteTable -> Assertion
@@ -180,8 +183,11 @@ testBind TestTransport{..} rtable = do
180 runProcess node $ do
183 runProcess node $ do
181 us <- getSelfPid
184 us <- getSelfPid
182 join . unClosure $ sendFac 6 us
185 join . unClosure $ sendFac 6 us
183− (720 :: Int) <- expect
186+ (i :: Int) <- expect
184 liftIO $ putMVar done ()
187 liftIO $ putMVar done ()
188+ if i == 720
189+ then return ()
190+ else error "Something went horribly wrong"
185 takeMVar done
191 takeMVar done
186
192
187testSendPureClosure :: TestTransport -> RemoteTable -> Assertion
193testSendPureClosure :: TestTransport -> RemoteTable -> Assertion
@@ -194,7 +200,7 @@ testSendPureClosure TestTransport{..} rtable = do
194 addr <- forkProcess node $ do
200 addr <- forkProcess node $ do
195 cl <- expect
201 cl <- expect
196 fn <- unClosure cl :: Process (Int -> Int)
202 fn <- unClosure cl :: Process (Int -> Int)
197− 13 <- return $ fn 6
203+ (_ :: Int) <- return $ fn 6
198 liftIO $ putMVar serverDone ()
204 liftIO $ putMVar serverDone ()
199 putMVar serverAddr addr
205 putMVar serverAddr addr
200
206
@@ -218,8 +224,11 @@ testSendIOClosure TestTransport{..} rtable = do
218 liftIO $ do
224 liftIO $ do
219 someMVar <- newEmptyMVar
225 someMVar <- newEmptyMVar
220 io someMVar
226 io someMVar
221− 5 <- readMVar someMVar
227+ i <- readMVar someMVar
222 putMVar serverDone ()
228 putMVar serverDone ()
229+ if i == 5
230+ then return ()
231+ else error "Something went horribly wrong"
223 putMVar serverAddr addr
232 putMVar serverAddr addr
224
233
225 forkIO $ do
234 forkIO $ do
@@ -248,8 +257,10 @@ testSendProcClosure TestTransport{..} rtable = do
248 runProcess node $ do
257 runProcess node $ do
249 pid <- getSelfPid
258 pid <- getSelfPid
250 send theirAddr (cpSend $(mkStatic 'sdictInt) pid)
259 send theirAddr (cpSend $(mkStatic 'sdictInt) pid)
251− 5 <- expect :: Process Int
260+ i <- expect :: Process Int
261+ if i == 5
252− liftIO $ putMVar clientDone ()
262+ then liftIO $ putMVar clientDone ()
263+ else error "Something went horribly wrong"
253
264
254 takeMVar clientDone
265 takeMVar clientDone
255
266
@@ -269,8 +280,9 @@ testSpawn TestTransport{..} rtable = do
269 pid <- getSelfPid
280 pid <- getSelfPid
270 pid' <- spawn nid (sendPidClosure pid)
281 pid' <- spawn nid (sendPidClosure pid)
271 pid'' <- expect
282 pid'' <- expect
272− True <- return $ pid' == pid''
283+ if pid' == pid''
273− liftIO $ putMVar clientDone ()
284+ then liftIO $ putMVar clientDone ()
285+ else error "Something went horribly wrong"
274
286
275 takeMVar clientDone
287 takeMVar clientDone
276
288
@@ -294,8 +306,9 @@ testSpawnRace TestTransport{..} rtable = do
294 spawnLocal $ spawn (localNodeId node2) (sendPidClosure pid) >>= send pid
306 spawnLocal $ spawn (localNodeId node2) (sendPidClosure pid) >>= send pid
295 pid' <- expect :: Process ProcessId
307 pid' <- expect :: Process ProcessId
296 pid'' <- expect :: Process ProcessId
308 pid'' <- expect :: Process ProcessId
297− True <- return $ pid' == pid''
309+ if pid' == pid''
298− return ()
310+ then return ()
311+ else error "Something went horribly wrong"
299
312
300 where
313 where
301
314
@@ -305,14 +318,16 @@ testSpawnRace TestTransport{..} rtable = do
305 wrapEP e =
318 wrapEP e =
306 e { NT.connect = \x y z -> do
319 e { NT.connect = \x y z -> do
307 healthy <- newIORef True
320 healthy <- newIORef True
308− fmap (fmap $ wrapConnection healthy) $ NT.connect e x y z
321+ fmap (fmap $ wrapConnection healthy e x) $ NT.connect e x y z
309 }
322 }
310
323
324+ wrapConnection :: IORef Bool -> NT.EndPoint -> NT.EndPointAddress
311− wrapConnection :: IORef Bool -> NT.Connection -> NT.Connection
325+ -> NT.Connection -> NT.Connection
312− wrapConnection healthy (NT.Connection s closeC) =
326+ wrapConnection healthy e remoteAddr (NT.Connection s closeC) =
313 flip NT.Connection closeC $ \msg -> do
327 flip NT.Connection closeC $ \msg -> do
314 when (msg == messageToPayload (createMessage ())) $ do
328 when (msg == messageToPayload (createMessage ())) $ do
315 writeIORef healthy False
329 writeIORef healthy False
330+ testBreakConnection (NT.address e) remoteAddr
316 isHealthy <- readIORef healthy
331 isHealthy <- readIORef healthy
317 if isHealthy then s msg
332 if isHealthy then s msg
318 else return $ Left $ NT.TransportError NT.SendFailed ""
333 else return $ Left $ NT.TransportError NT.SendFailed ""
@@ -330,8 +345,10 @@ testCall TestTransport{..} rtable = do
330 node <- newLocalNode testTransport rtable
345 node <- newLocalNode testTransport rtable
331 nid <- readMVar serverNodeAddr
346 nid <- readMVar serverNodeAddr
332 runProcess node $ do
347 runProcess node $ do
333− (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorialClosure 5)
348+ (a :: Int) <- call $(mkStatic 'sdictInt) nid (factorialClosure 5)
349+ if a == 120
334− liftIO $ putMVar clientDone ()
350+ then liftIO $ putMVar clientDone ()
351+ else error "something went horribly wrong"
335
352
336 takeMVar clientDone
353 takeMVar clientDone
337
354
@@ -348,8 +365,10 @@ testCallBind TestTransport{..} rtable = do
348 node <- newLocalNode testTransport rtable
365 node <- newLocalNode testTransport rtable
349 nid <- readMVar serverNodeAddr
366 nid <- readMVar serverNodeAddr
350 runProcess node $ do
367 runProcess node $ do
351− (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorial' 5)
368+ (a :: Int) <- call $(mkStatic 'sdictInt) nid (factorial' 5)
369+ if a == 120
352− liftIO $ putMVar clientDone ()
370+ then liftIO $ putMVar clientDone ()
371+ else error "Something went horribly wrong"
353
372
354 takeMVar clientDone
373 takeMVar clientDone
355
374
@@ -360,9 +379,11 @@ testSeq TestTransport{..} rtable = do
360 runProcess node $ do
379 runProcess node $ do
361 us <- getSelfPid
380 us <- getSelfPid
362 join . unClosure $ sendFac 5 us `seqCP` sendFac 6 us
381 join . unClosure $ sendFac 5 us `seqCP` sendFac 6 us
363− 120 :: Int <- expect
382+ a :: Int <- expect
364− 720 :: Int <- expect
383+ b :: Int <- expect
384+ if a == 120 && b == 720
365− liftIO $ putMVar done ()
385+ then liftIO $ putMVar done ()
386+ else error "Something went horribly wrong"
366 takeMVar done
387 takeMVar done
367
388
368-- Test 'spawnSupervised'
389-- Test 'spawnSupervised'
@@ -391,25 +412,33 @@ testSpawnSupervised TestTransport{..} rtable = do
391 throw supervisorDeath
412 throw supervisorDeath
392
413
393 forkProcess node2 $ do
414 forkProcess node2 $ do
394− [super, child] <- liftIO $ mapM readMVar [superPid, childPid]
415+ res <- liftIO $ mapM readMVar [superPid, childPid]
416+ case res of
417+ [super, child] -> do
395− ref <- monitor child
418+ ref <- monitor child
396− self <- getSelfPid
419+ self <- getSelfPid
397− let waitForMOrL = do
420+ let waitForMOrL = do
398− liftIO $ threadDelay 10000
421+ liftIO $ threadDelay 10000
399− mpinfo <- getProcessInfo child
422+ mpinfo <- getProcessInfo child
400− case mpinfo of
423+ case mpinfo of
401− Nothing -> waitForMOrL
424+ Nothing -> waitForMOrL
402− Just pinfo ->
425+ Just pinfo ->
403− unless (isJust $ lookup self (infoMonitors pinfo)) waitForMOrL
426+ unless (isJust $ lookup self (infoMonitors pinfo)) waitForMOrL
404− waitForMOrL
427+ waitForMOrL
405− liftIO $ putMVar linkUp ()
428+ liftIO $ putMVar linkUp ()
406− -- because monitor message was sent before message to process
429+ -- because monitor message was sent before message to process
407− -- we hope that it will be processed before
430+ -- we hope that it will be processed before
431+ res' <- expect
432+ case res' of
408− ProcessMonitorNotification ref' pid' (DiedException e) <- expect
433+ (ProcessMonitorNotification ref' pid' (DiedException e)) ->
409− True <- return $ ref' == ref
410− && pid' == child
434+ if (ref' == ref && pid' == child &&
435+ e == show (ProcessLinkException super
411− && e == show (ProcessLinkException super (DiedException (show supervisorDeath)))
436+ (DiedException (show supervisorDeath))))
412− liftIO $ putMVar thirdProcessDone ()
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"
413
442
414 takeMVar thirdProcessDone
443 takeMVar thirdProcessDone
415 where
444 where
@@ -424,8 +453,10 @@ testSpawnInvalid TestTransport{..} rtable = do
424 (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)
453 (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)
425 ProcessMonitorNotification ref' pid' _reason <- expect
454 ProcessMonitorNotification ref' pid' _reason <- expect
426 -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure
455 -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure
427− True <- return $ ref' == ref && pid == pid'
456+ res <- return $ ref' == ref && pid == pid'
457+ if res == True
428− liftIO $ putMVar done ()
458+ then liftIO $ putMVar done ()
459+ else error "Something went horribly wrong"
429 takeMVar done
460 takeMVar done
430
461
431testClosureExpect :: TestTransport -> RemoteTable -> Assertion
462testClosureExpect :: TestTransport -> RemoteTable -> Assertion
@@ -437,8 +468,10 @@ testClosureExpect TestTransport{..} rtable = do
437 us <- getSelfPid
468 us <- getSelfPid
438 them <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us
469 them <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us
439 send them (1234 :: Int)
470 send them (1234 :: Int)
440− (1234 :: Int) <- expect
471+ (res :: Int) <- expect
472+ if res == 1234
441− liftIO $ putMVar done ()
473+ then liftIO $ putMVar done ()
474+ else error "Something went horribly wrong"
442 takeMVar done
475 takeMVar done
443
476
444testSpawnChannel :: TestTransport -> RemoteTable -> Assertion
477testSpawnChannel :: TestTransport -> RemoteTable -> Assertion
@@ -463,8 +496,10 @@ testTDict TestTransport{..} rtable = do
463 done <- newEmptyMVar
496 done <- newEmptyMVar
464 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
497 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
465 forkProcess node1 $ do
498 forkProcess node1 $ do
466− True <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))
499+ res <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))
500+ if res == True
467− liftIO $ putMVar done ()
501+ then liftIO $ putMVar done ()
502+ else error "Something went horribly wrong..."
468 takeMVar done
503 takeMVar done
469
504
470testFib :: TestTransport -> RemoteTable -> Assertion
505testFib :: TestTransport -> RemoteTable -> Assertion
@@ -475,8 +510,11 @@ testFib TestTransport{..} rtable = do
475 forkProcess (head nodes) $ do
510 forkProcess (head nodes) $ do
476 (sport, rport) <- newChan
511 (sport, rport) <- newChan
477 spawnLocal $ dfib (map localNodeId nodes, sport, 10)
512 spawnLocal $ dfib (map localNodeId nodes, sport, 10)
478− 55 <- receiveChan rport :: Process Integer
513+ ff <- receiveChan rport :: Process Integer
479 liftIO $ putMVar done ()
514 liftIO $ putMVar done ()
515+ if ff /= 55
516+ then die $ "Something went horribly wrong"
517+ else return ()
480
518
481 takeMVar done
519 takeMVar done
482
520
@@ -484,7 +522,7 @@ testSpawnReconnect :: TestTransport -> RemoteTable -> Assertion
484testSpawnReconnect testtrans@TestTransport{..} rtable = do
522testSpawnReconnect testtrans@TestTransport{..} rtable = do
485 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
523 [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
486 let nid1 = localNodeId node1
524 let nid1 = localNodeId node1
487− nid2 = localNodeId node2
525+ -- nid2 = localNodeId node2
488 done <- newEmptyMVar
526 done <- newEmptyMVar
489 iv <- newMVar (0 :: Int)
527 iv <- newMVar (0 :: Int)
490
528
@@ -501,9 +539,12 @@ testSpawnReconnect testtrans@TestTransport{..} rtable = do
501 liftIO $ threadDelay 100000
539 liftIO $ threadDelay 100000
502
540
503 count <- liftIO $ takeMVar iv
541 count <- liftIO $ takeMVar iv
504− True <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'
542+ res <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'
505
543
506 liftIO $ putMVar done ()
544 liftIO $ putMVar done ()
545+ if res /= True
546+ then error "Something went horribly wrong"
547+ else return ()
507
548
508 takeMVar done
549 takeMVar done
509
550
distributed-process-tests/src/Control/Distributed/Process/Tests/Internal/Utils.hs+16−5
@@ -20,6 +20,7 @@ module Control.Distributed.Process.Tests.Internal.Utils
20 -- ping !
20 -- ping !
21 , Ping(Ping)
21 , Ping(Ping)
22 , ping
22 , ping
23+ , pause
23 , shouldBe
24 , shouldBe
24 , shouldMatch
25 , shouldMatch
25 , shouldContain
26 , shouldContain
@@ -73,15 +74,16 @@ import Control.Concurrent
73import Control.Concurrent.MVar
74import Control.Concurrent.MVar
74 ( putMVar
75 ( putMVar
75 )
76 )
76−import Control.Distributed.Process
77+import Control.Distributed.Process hiding (finally, catch)
77import Control.Distributed.Process.Node
78import Control.Distributed.Process.Node
78import Control.Distributed.Process.Serializable()
79import Control.Distributed.Process.Serializable()
79
80
80import Control.Exception (AsyncException(ThreadKilled), SomeException)
81import Control.Exception (AsyncException(ThreadKilled), SomeException)
81−import Control.Monad (forever)
82+import Control.Monad (forever, void)
83+import Control.Monad.Catch (finally, catch)
82import Control.Monad.STM (atomically)
84import Control.Monad.STM (atomically)
83import Control.Rematch hiding (match)
85import Control.Rematch hiding (match)
84−import Control.Rematch.Run
86+import Control.Rematch.Run
85import Data.Binary
87import Data.Binary
86import Data.Typeable (Typeable)
88import Data.Typeable (Typeable)
87
89
@@ -89,6 +91,7 @@ import Test.HUnit (Assertion, assertFailure)
89import Test.HUnit.Base (assertBool)
91import Test.HUnit.Base (assertBool)
90
92
91import GHC.Generics
93import GHC.Generics
94+import System.Timeout (timeout)
92
95
93-- | A mutable cell containing a test result.
96-- | A mutable cell containing a test result.
94type TestResult a = MVar a
97type TestResult a = MVar a
@@ -107,10 +110,18 @@ data TestProcessControl = Stop | Go | Report ProcessId
107
110
108instance Binary TestProcessControl where
111instance Binary TestProcessControl where
109
112
113+data Private = Private
114+ deriving (Typeable, Generic)
115+instance Binary Private where
116+
110-- | Does exactly what it says on the tin, doing so in the @Process@ monad.
117-- | Does exactly what it says on the tin, doing so in the @Process@ monad.
111noop :: Process ()
118noop :: Process ()
112noop = return ()
119noop = return ()
113
120
121+pause :: Int -> Process ()
122+pause delay =
123+ void $ receiveTimeout delay [ match (\Private -> return ()) ]
124+
114synchronisedAssertion :: Eq a
125synchronisedAssertion :: Eq a
115 => String
126 => String
116 -> LocalNode
127 -> LocalNode
@@ -223,10 +234,10 @@ testProcessReport pid = do
223tryRunProcess :: LocalNode -> Process () -> IO ()
234tryRunProcess :: LocalNode -> Process () -> IO ()
224tryRunProcess node p = do
235tryRunProcess node p = do
225 tid <- liftIO myThreadId
236 tid <- liftIO myThreadId
237+ void $ timeout (1000000 * 60 * 5 :: Int) $
226− runProcess node $ catch p (\e -> liftIO $ throwTo tid (e::SomeException))
238+ runProcess node $ catch p (\e -> liftIO $ throwTo tid (e::SomeException))
227
239
228tryForkProcess :: LocalNode -> Process () -> IO ProcessId
240tryForkProcess :: LocalNode -> Process () -> IO ProcessId
229tryForkProcess node p = do
241tryForkProcess node p = do
230 tid <- liftIO myThreadId
242 tid <- liftIO myThreadId
231 forkProcess node $ catch p (\e -> liftIO $ throwTo tid (e::SomeException))
243 forkProcess node $ catch p (\e -> liftIO $ throwTo tid (e::SomeException))
232−
distributed-process-tests/src/Control/Distributed/Process/Tests/Mx.hs+385−129
@@ -1,12 +1,23 @@
1−{-# LANGUAGE DeriveGeneric #-}
1+{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE ParallelListComp #-}
3+
2module Control.Distributed.Process.Tests.Mx (tests) where
4module Control.Distributed.Process.Tests.Mx (tests) where
3
5
4import Control.Distributed.Process.Tests.Internal.Utils
6import Control.Distributed.Process.Tests.Internal.Utils
5import Network.Transport.Test (TestTransport(..))
7import Network.Transport.Test (TestTransport(..))
6−
8+import Control.Distributed.Process hiding (bracket, finally, try)
7−import Control.Concurrent (threadDelay)
8−import Control.Distributed.Process
9+import Control.Distributed.Process.Internal.Types
10+ ( ProcessExitException(..)
11+ , unsafeCreateUnencodedMessage
12+ )
9import Control.Distributed.Process.Node
13import Control.Distributed.Process.Node
14+import qualified Control.Distributed.Process.UnsafePrimitives as Unsafe
15+ ( send
16+ , nsend
17+ , nsendRemote
18+ , usend
19+ , sendChan
20+ )
10import Control.Distributed.Process.Management
21import Control.Distributed.Process.Management
11 ( MxEvent(..)
22 ( MxEvent(..)
12 , MxAgentId(..)
23 , MxAgentId(..)
@@ -21,19 +32,15 @@ import Control.Distributed.Process.Management
21 , mxUpdateLocal
32 , mxUpdateLocal
22 , mxNotify
33 , mxNotify
23 , mxBroadcast
34 , mxBroadcast
24− , mxGetId
25− , mxGet
26− , mxSet
27− , mxClear
28− , mxPurgeTable
29− , mxDropTable
30 )
35 )
31−import Control.Monad (void)
36+import Control.Monad (void, unless)
37+import Control.Monad.Catch(finally, bracket, try)
38+import Control.Rematch (equalTo)
32import Data.Binary
39import Data.Binary
33−import Data.List (find, sort)
40+import Data.List (find, sort, intercalate)
34−import Data.Maybe (isJust)
41+import Data.Maybe (isJust, fromJust, isNothing, fromMaybe, catMaybes)
35import Data.Typeable
42import Data.Typeable
36−import GHC.Generics
43+import GHC.Generics hiding (from)
37#if ! MIN_VERSION_base(4,6,0)
44#if ! MIN_VERSION_base(4,6,0)
38import Prelude hiding (catch, log)
45import Prelude hiding (catch, log)
39#endif
46#endif
@@ -49,7 +56,17 @@ data Publish = Publish
49
56
50instance Binary Publish where
57instance Binary Publish where
51
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+
52−testAgentBroadcast :: TestResult () -> Process ()
69+testAgentBroadcast :: TestResult (Maybe ()) -> Process ()
53testAgentBroadcast result = do
70testAgentBroadcast result = do
54 (resultSP, resultRP) <- newChan :: Process (SendPort (), ReceivePort ())
71 (resultSP, resultRP) <- newChan :: Process (SendPort (), ReceivePort ())
55
72
@@ -64,7 +81,7 @@ testAgentBroadcast result = do
64 mxNotify ()
81 mxNotify ()
65 -- Once the publisher has seen our message, it will broadcast the Publish
82 -- Once the publisher has seen our message, it will broadcast the Publish
66 -- and the consumer will see that and send the result to our typed channel.
83 -- and the consumer will see that and send the result to our typed channel.
67− stash result =<< receiveChan resultRP
84+ stash result =<< receiveChanTimeout 10000000 resultRP
68
85
69 kill publisher "finished"
86 kill publisher "finished"
70 kill consumer "finished"
87 kill consumer "finished"
@@ -72,7 +89,7 @@ testAgentBroadcast result = do
72testAgentDualInput :: TestResult (Maybe Int) -> Process ()
89testAgentDualInput :: TestResult (Maybe Int) -> Process ()
73testAgentDualInput result = do
90testAgentDualInput result = do
74 (sp, rp) <- newChan
91 (sp, rp) <- newChan
75− _ <- mxAgent (MxAgentId "sum-agent") (0 :: Int) [
92+ s <- mxAgent (MxAgentId "sum-agent") (0 :: Int) [
76 mxSink $ (\(i :: Int) -> do
93 mxSink $ (\(i :: Int) -> do
77 mxSetLocal . (+i) =<< mxGetLocal
94 mxSetLocal . (+i) =<< mxGetLocal
78 i' <- mxGetLocal
95 i' <- mxGetLocal
@@ -82,6 +99,8 @@ testAgentDualInput result = do
82 else mxReady)
99 else mxReady)
83 ]
100 ]
84
101
102+ mRef <- monitor s
103+
85 mxNotify (1 :: Int)
104 mxNotify (1 :: Int)
86 nsend "sum-agent" (3 :: Int)
105 nsend "sum-agent" (3 :: Int)
87 mxNotify (2 :: Int)
106 mxNotify (2 :: Int)
@@ -89,6 +108,10 @@ testAgentDualInput result = do
89 mxNotify (5 :: Int)
108 mxNotify (5 :: Int)
90
109
91 stash result =<< receiveChanTimeout 10000000 rp
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)
92
115
93testAgentPrioritisation :: TestResult [String] -> Process ()
116testAgentPrioritisation :: TestResult [String] -> Process ()
94testAgentPrioritisation result = do
117testAgentPrioritisation result = do
@@ -100,12 +123,12 @@ testAgentPrioritisation result = do
100
123
101 let name = "prioritising-agent"
124 let name = "prioritising-agent"
102 (sp, rp) <- newChan
125 (sp, rp) <- newChan
103− void $ mxAgent (MxAgentId name) ["first"] [
126+ s <- mxAgent (MxAgentId name) ["first"] [
104− mxSink (\(s :: String) -> do
127+ mxSink (\(s :: String) -> do
105− mxUpdateLocal ((s:))
128+ mxUpdateLocal (s:)
106 st <- mxGetLocal
129 st <- mxGetLocal
107 case length st of
130 case length st of
108− n | n == 5 -> do liftMX $ sendChan sp st
131+ n | n == 5 -> do liftMX $ sendChan sp (sort st)
109 mxDeactivate "finished"
132 mxDeactivate "finished"
110 _ -> mxReceive -- go to the mailbox
133 _ -> mxReceive -- go to the mailbox
111 )
134 )
@@ -116,23 +139,33 @@ testAgentPrioritisation result = do
116 mxNotify "fourth"
139 mxNotify "fourth"
117 nsend name "fifth"
140 nsend name "fifth"
118
141
119− stash result . sort =<< receiveChan rp
142+ stash result . fromMaybe [] =<< receiveChanTimeout 10000000 rp
143+ awaitExit s
120
144
121testAgentMailboxHandling :: TestResult (Maybe ()) -> Process ()
145testAgentMailboxHandling :: TestResult (Maybe ()) -> Process ()
122testAgentMailboxHandling result = do
146testAgentMailboxHandling result = do
123 (sp, rp) <- newChan
147 (sp, rp) <- newChan
124− agent <- mxAgent (MxAgentId "listener-agent") () [
148+ agent <- mxAgent (MxAgentId "mailbox-agent") () [
125 mxSink $ \() -> (liftMX $ sendChan sp ()) >> mxReady
149 mxSink $ \() -> (liftMX $ sendChan sp ()) >> mxReady
126 ]
150 ]
127
151
128− nsend "listener-agent" ()
152+ nsend "mailbox-agent" ()
129
153
130 stash result =<< receiveChanTimeout 1000000 rp
154 stash result =<< receiveChanTimeout 1000000 rp
131 kill agent "finished"
155 kill agent "finished"
132
156
133testAgentEventHandling :: TestResult Bool -> Process ()
157testAgentEventHandling :: TestResult Bool -> Process ()
134testAgentEventHandling result = do
158testAgentEventHandling 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+
135 let initState = [] :: [MxEvent]
167 let initState = [] :: [MxEvent]
168+ (rc, rs) <- newChan
136 agentPid <- mxAgent (MxAgentId "lifecycle-listener-agent") initState [
169 agentPid <- mxAgent (MxAgentId "lifecycle-listener-agent") initState [
137 (mxSink $ \ev -> do
170 (mxSink $ \ev -> do
138 st <- mxGetLocal
171 st <- mxGetLocal
@@ -141,7 +174,7 @@ testAgentEventHandling result = do
141 (MxSpawned _) -> mxSetLocal (ev:st)
174 (MxSpawned _) -> mxSetLocal (ev:st)
142 (MxProcessDied _ _) -> mxSetLocal (ev:st)
175 (MxProcessDied _ _) -> mxSetLocal (ev:st)
143 _ -> return ()
176 _ -> return ()
144− act >> mxReady),
177+ act >> (liftMX $ sendChan rc ()) >> mxReady),
145 (mxSink $ \(ev, sp :: SendPort Bool) -> do
178 (mxSink $ \(ev, sp :: SendPort Bool) -> do
146 st <- mxGetLocal
179 st <- mxGetLocal
147 let found =
180 let found =
@@ -161,126 +194,349 @@ testAgentEventHandling result = do
161 mxReady)
194 mxReady)
162 ]
195 ]
163
196
164− _ <- monitor agentPid
197+ -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
198+ faff 2000000 rs
199+
165− (sp, rp) <- newChan
200+ (sp, rp) <- newChan :: Process (SendPort (), ReceivePort ())
201+
166− pid <- spawnLocal $ sendChan sp ()
202+ pid <- spawnLocal $ expect >>= sendChan sp
167− () <- receiveChan rp
168
203
169 -- By waiting for a monitor notification, we have a
204 -- By waiting for a monitor notification, we have a
170− -- higher probably that the agent has seen
205+ -- higher probably that the agent has seen the spawn and died events
171 monitor pid
206 monitor pid
207+
208+ send pid ()
209+ rct <- receiveChanTimeout 10000000 rp
210+ unless (isJust rct) $ die "No response on channel"
211+
172− receiveWait [ match (\(ProcessMonitorNotification _ _ _) -> return ()) ]
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
173
217
174 (replyTo, reply) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
218 (replyTo, reply) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
175 mxNotify (MxSpawned pid, replyTo)
219 mxNotify (MxSpawned pid, replyTo)
176 mxNotify (MxProcessDied pid DiedNormal, replyTo)
220 mxNotify (MxProcessDied pid DiedNormal, replyTo)
177
221
178− seenAlive <- receiveChan reply
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+
179− seenDead <- receiveChan reply
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+ ]
180
437
181− stash result $ seenAlive && seenDead
438+ case mNode of
439+ Nothing -> void $ spawnLocal (proc sp)
440+ Just remoteNode -> void $ liftIO $ forkProcess remoteNode $ proc sp
182
441
183−testAgentPublication :: TestResult (Maybe Publish) -> Process ()
442+ p1 <- receiveChanTimeout 2000000 rp
184−testAgentPublication result = do
443+ unless (isJust p1) $ die "Timed out waiting for ProcessId"
185− (syncChan, rp) <- newChan
444+ res <- try $ test (fromJust p1) sink
186− let ourId = MxAgentId "publication-agent"
187− agentPid <- mxAgent ourId () [
188− mxSink $ \() -> do
189− selfId <- mxGetId
190− liftMX $ mxSet selfId "publish" Publish >> sendChan syncChan ()
191− mxReady
192− ]
193
445
194− mxNotify ()
446+ kill agent "bye"
195− () <- receiveChan rp
447+ kill (fromJust p1) "bye"
196
448
197− stash result =<< mxGet ourId "publish"
449+ case res of
450+ Left (ProcessExitException _ m) -> (liftIO $ putStrLn $ "SomeException-" ++ show m) >> die m
451+ Right tr -> tr `shouldBe` equalTo True
198
452
199− mref <- monitor agentPid
200− kill agentPid "finished"
201− receiveWait [
202− matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mref)
203− ((\_ -> return ()))
204− ]
205
453
206−testAgentTableClear :: Int -> TestResult (Maybe Int, Maybe Int) -> Process ()
454+ where
207−testAgentTableClear val result =
455+ agentLabel s = "mx-unsafe-check-agent-" ++ show s
208− let tId = (MxAgentId "agent-1")
456+ proc sp' = getSelfPid >>= sendChan sp' >> go Nothing
209− tKey = "key-1" in do
457+
210− mxSet tId tKey val
458+ go :: Maybe (ReceivePort ()) -> Process ()
211− get1 <- mxGet tId tKey
459+ go Nothing = receiveTimeout 5000000 [ match replyChannel ] >>= go
212− mxClear tId tKey
460+ go c@(Just c') = receiveWait [ matchChan c' return ] >> go c
213− get2 <- mxGet tId tKey
461+
214− stash result (get1, get2)
462+ replyChannel p' = do
215−
463+ (s, r) <- newChan
216−testAgentTablePurge :: TestResult (Maybe Int) -> Process ()
464+ send p' s
217−testAgentTablePurge result =
465+ return r
218− let tId = MxAgentId "agent-2"
219− tKey = "key-2" in do
220− mxSet tId tKey (12345 :: Int)
221− mxPurgeTable tId
222− stash result =<< mxGet tId tKey
223−
224−testAgentTableDelete :: Int
225− -> TestResult (Maybe Int, Maybe Int, Maybe Int)
226− -> Process ()
227−testAgentTableDelete val result =
228− let tId = (MxAgentId "agent-3")
229− tKey = "key-3" in do
230− mxSet tId tKey val
231− get1 <- mxGet tId tKey
232− mxDropTable tId
233− get2 <- mxGet tId tKey
234− mxSet tId tKey val
235− get3 <- mxGet tId tKey
236− stash result (get1, get2, get3)
237
466
238tests :: TestTransport -> IO [Test]
467tests :: TestTransport -> IO [Test]
239tests TestTransport{..} = do
468tests TestTransport{..} = do
240 node1 <- newLocalNode testTransport initRemoteTable
469 node1 <- newLocalNode testTransport initRemoteTable
470+ node2 <- newLocalNode testTransport initRemoteTable
241 return [
471 return [
242− testGroup "Mx Agents" [
472+ testGroup "MxAgents" [
243− testCase "Event Handling"
473+ testCase "EventHandling"
244− (delayedAssertion
474+ (delayedAssertion
245− "expected True, but events where not as expected"
475+ "expected True, but events where not as expected"
246− node1 True testAgentEventHandling)
476+ node1 True testAgentEventHandling)
247− , testCase "Inter-Agent Broadcast"
477+ , testCase "InterAgentBroadcast"
248− (delayedAssertion
478+ (delayedAssertion
249− "expected (), but no broadcast was received"
479+ "expected (), but no broadcast was received"
250− node1 () testAgentBroadcast)
480+ node1 (Just ()) testAgentBroadcast)
251− , testCase "Agent Mailbox Handling"
481+ , testCase "AgentMailboxHandling"
252− (delayedAssertion
482+ (delayedAssertion
253− "expected (Just ()), but no regular (mailbox) input was handled"
483+ "expected (Just ()), but no regular (mailbox) input was handled"
254− node1 (Just ()) testAgentMailboxHandling)
484+ node1 (Just ()) testAgentMailboxHandling)
255− , testCase "Agent Dual Input Handling"
485+ , testCase "AgentDualInputHandling"
256− (delayedAssertion
486+ (delayedAssertion
257− "expected sum = 15, but the result was Nothing"
487+ "expected sum = 15, but the result was Nothing"
258− node1 (Just 15 :: Maybe Int) testAgentDualInput)
488+ node1 (Just 15 :: Maybe Int) testAgentDualInput)
259− , testCase "Agent Input Prioritisation"
489+ , testCase "AgentInputPrioritisation"
260− (delayedAssertion
490+ (delayedAssertion
261− "expected [first, second, third, fourth, fifth], but result diverged"
491+ "expected [first, second, third, fourth, fifth], but result diverged"
262− node1 (sort ["first", "second",
492+ node1 (sort ["first", "second",
263− "third", "fourth",
493+ "third", "fourth",
264− "fifth"]) testAgentPrioritisation)
494+ "fifth"]) testAgentPrioritisation)
265− ],
495+ ]
266− testGroup "Mx Global Properties" [
496+ , testGroup "MxEvents" [
267− testCase "Global Property Publication"
497+ testCase "NameRegistrationEvents"
268− (delayedAssertion
498+ (delayedAssertion
269− "expected (Just Publish), but no table entry was found"
499+ "expected registration events to map to the correct ProcessId"
270− node1 (Just Publish) testAgentPublication)
500+ node1 () testMxRegEvents)
271− , testCase "Clearing Global Properties"
501+ , testCase "PostDeathNameUnRegistrationEvents"
272− (delayedAssertion
502+ (delayedAssertion
273− "expected (Just 1024, Nothing): invalid table entry found!"
503+ "expected process deaths to result in unregistration events"
274− node1 (Just 1024, Nothing) (testAgentTableClear 1024))
504+ node1 () (testMxRegMon node2))
275− , testCase "Purging Global Tables"
505+ , testGroup "SendEvents" $ buildTestCases node1 node2
276− (delayedAssertion
506+ ]
277− "expected Nothing, but a table entry was found"
507+ ]
278− node1 Nothing testAgentTablePurge)
508+ where
279− , testCase "Deleting and (Re)Creating Global Tables"
509+ buildTestCases n1 n2 = let nid = localNodeId n2 in build n1 n2 [
280− (delayedAssertion
510+ ("NSend", [
281− "expected (Just 15, Nothing, Just 15): invalid table entry found!"
511+ ("nsend", testNSend nsend)
282− node1 (Just 15, Nothing, Just 15) (testAgentTableDelete 15))
512+ , ("Unsafe.nsend", testNSend Unsafe.nsend)
283− -- Wait for other tests to finish.
513+ , ("nsendRemote", testNSend (nsendRemote nid))
284− , testCase "Wait" $
514+ , ("Unsafe.nsendRemote", testNSend (Unsafe.nsendRemote nid))
285− threadDelay 100000
515+ ])
286− ]]
516+ , ("Send", [
517+ ("send", testSend send)
518+ , ("Unsafe.send", testSend Unsafe.send)
519+ , ("usend", testSend usend)
520+ , ("Unsafe.usend", testSend Unsafe.usend)
521+ , ("sendChan", testChan sendChan)
522+ , ("Unsafe.sendChan", testChan Unsafe.sendChan)
523+ ])
524+ , ("Forward", [
525+ ("forward", testSend (\p m -> forward (unsafeCreateUnencodedMessage m) p))
526+ , ("uforward", testSend (\p m -> uforward (unsafeCreateUnencodedMessage m) p))
527+ ])
528+ ]
529+
530+ build :: LocalNode
531+ -> LocalNode
532+ -> [(String, [(String, (Maybe LocalNode -> Process ()))])]
533+ -> [Test]
534+ build n ln specs =
535+ [ testGroup (intercalate "-" [groupName, caseSuffix]) [
536+ testCase (intercalate "-" [caseName, caseSuffix])
537+ (runProcess n (caseImpl caseNode))
538+ | (caseName, caseImpl) <- groupCases
539+ ]
540+ | (groupName, groupCases) <- specs
541+ , (caseSuffix, caseNode) <- [("RemotePid", Just ln), ("LocalPid", Nothing)]
542+ ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Receive.hs+8−12
@@ -1,4 +1,4 @@
1−{-# LANGUAGE TemplateHaskell, DeriveDataTypeable #-}
1+{-# LANGUAGE TemplateHaskell #-}
2{-# OPTIONS_GHC -Wall #-}
2{-# OPTIONS_GHC -Wall #-}
3
3
4-- | XXX test doesn't work, because failure exceptions don't get propagated. The
4-- | XXX test doesn't work, because failure exceptions don't get propagated. The
@@ -10,16 +10,12 @@ import Network.Transport.Test (TestTransport(..))
10
10
11import Network.Transport (Transport)
11import Network.Transport (Transport)
12import Control.Distributed.Process
12import Control.Distributed.Process
13−import Control.Distributed.Process.Closure
14import Control.Distributed.Process.Node
13import Control.Distributed.Process.Node
15
14
16import Control.Monad
15import Control.Monad
17−import Text.Printf
18−import Data.Binary
19−import Data.Typeable
20
16
21import Test.HUnit (Assertion, (@?=))
17import Test.HUnit (Assertion, (@?=))
22−import Test.Framework (Test, defaultMain)
18+import Test.Framework (Test)
23import Test.Framework.Providers.HUnit (testCase)
19import Test.Framework.Providers.HUnit (testCase)
24
20
25-- Tests:
21-- Tests:
@@ -46,7 +42,7 @@ recTest2 :: ReceivePort ()
46 -> SendPort String
42 -> SendPort String
47 -> ReceivePort String -> ReceivePort String
43 -> ReceivePort String -> ReceivePort String
48 -> Process ()
44 -> Process ()
49−recTest2 wait sync r1 r2 = do
45+recTest2 wait sync r1 _ = do
50 forever $ do
46 forever $ do
51 receiveChan wait
47 receiveChan wait
52 r <- receiveWait
48 r <- receiveWait
@@ -59,7 +55,7 @@ recTest3 :: ReceivePort ()
59 -> SendPort String
55 -> SendPort String
60 -> ReceivePort String -> ReceivePort String
56 -> ReceivePort String -> ReceivePort String
61 -> Process ()
57 -> Process ()
62−recTest3 wait sync r1 r2 = do
58+recTest3 wait sync r1 _ = do
63 forever $ do
59 forever $ do
64 receiveChan wait
60 receiveChan wait
65 r <- receiveWait
61 r <- receiveWait
@@ -72,7 +68,7 @@ recTest4 :: ReceivePort ()
72 -> SendPort String
68 -> SendPort String
73 -> ReceivePort String -> ReceivePort String
69 -> ReceivePort String -> ReceivePort String
74 -> Process ()
70 -> Process ()
75−recTest4 wait sync r1 r2 = do
71+recTest4 wait sync r1 _ = do
76 forever $ do
72 forever $ do
77 receiveChan wait
73 receiveChan wait
78 r <- receiveWait
74 r <- receiveWait
@@ -86,11 +82,11 @@ master :: Process ()
86master = do
82master = do
87 (waits,waitr) <- newChan
83 (waits,waitr) <- newChan
88 (syncs,syncr) <- newChan
84 (syncs,syncr) <- newChan
89− let go expect = do
85+ let go expected = do
90 sendChan waits ()
86 sendChan waits ()
91 r <- receiveChan syncr
87 r <- receiveChan syncr
92− liftIO $ print (r,expect, r == expect)
88+ liftIO $ print (r, expected, r == expected)
93− liftIO $ r @?= expect
89+ liftIO $ r @?= expected
94
90
95 liftIO $ putStrLn "---- Test 1 ----"
91 liftIO $ putStrLn "---- Test 1 ----"
96 (s1,r1) <- newChan
92 (s1,r1) <- newChan
distributed-process-tests/src/Control/Distributed/Process/Tests/Stats.hs+11−7
@@ -29,7 +29,7 @@ import Test.Framework.Providers.HUnit (testCase)
29
29
30testLocalDeadProcessInfo :: TestResult (Maybe ProcessInfo) -> Process ()
30testLocalDeadProcessInfo :: TestResult (Maybe ProcessInfo) -> Process ()
31testLocalDeadProcessInfo result = do
31testLocalDeadProcessInfo result = do
32− pid <- spawnLocal $ do "finish" <- expect; return ()
32+ pid <- spawnLocal $ do (_ :: String) <- expect; return ()
33 mref <- monitor pid
33 mref <- monitor pid
34 send pid "finish"
34 send pid "finish"
35 _ <- receiveWait [
35 _ <- receiveWait [
@@ -52,8 +52,10 @@ testLocalLiveProcessInfo result = do
52 link self
52 link self
53 mRef <- monitor self
53 mRef <- monitor self
54 stash mon mRef
54 stash mon mRef
55− "die" <- expect
55+ res <- expect
56− return ()
56+ case res of
57+ "die" -> return ()
58+ _ -> die $ "unexpected message received: " ++ res
57
59
58 monRef <- liftIO $ takeMVar mon
60 monRef <- liftIO $ takeMVar mon
59
61
@@ -84,9 +86,9 @@ testRemoteLiveProcessInfo TestTransport{..} node1 = do
84 -- our send op shouldn't overtake link or monitor requests AFAICT
86 -- our send op shouldn't overtake link or monitor requests AFAICT
85 -- so a little table tennis should get us synchronised properly
87 -- so a little table tennis should get us synchronised properly
86 send serverPid (self, "ping")
88 send serverPid (self, "ping")
87− "pong" <- expect
89+ pong <- expect
88 pInfo <- getProcessInfo serverPid
90 pInfo <- getProcessInfo serverPid
89− stash result $ pInfo /= Nothing
91+ stash result $ pong == "pong" && pInfo /= Nothing
90 where
92 where
91 launchRemote :: MVar ProcessId -> IO ()
93 launchRemote :: MVar ProcessId -> IO ()
92 launchRemote locMV = do
94 launchRemote locMV = do
@@ -97,8 +99,10 @@ testRemoteLiveProcessInfo TestTransport{..} node1 = do
97 _ <- receiveWait [
99 _ <- receiveWait [
98 match (\(pid, "ping") -> send pid "pong")
100 match (\(pid, "ping") -> send pid "pong")
99 ]
101 ]
100− "stop" <- expect
102+ res <- expect
101− return ()
103+ case res of
104+ "stop" -> return ()
105+ _ -> die $ "unexpected message received: " ++ res
102 return ()
106 return ()
103
107
104 withActiveRemote :: LocalNode
108 withActiveRemote :: LocalNode
distributed-process-tests/src/Control/Distributed/Process/Tests/Tracing.hs+113−8
@@ -5,6 +5,7 @@ module Control.Distributed.Process.Tests.Tracing (tests) where
5import Control.Distributed.Process.Tests.Internal.Utils
5import Control.Distributed.Process.Tests.Internal.Utils
6import Network.Transport.Test (TestTransport(..))
6import Network.Transport.Test (TestTransport(..))
7
7
8+import Control.Applicative ((<*))
8import Control.Concurrent (threadDelay)
9import Control.Concurrent (threadDelay)
9import Control.Concurrent.MVar
10import Control.Concurrent.MVar
10 ( MVar
11 ( MVar
@@ -19,9 +20,13 @@ import Control.Distributed.Process.Debug
19import Control.Distributed.Process.Management
20import Control.Distributed.Process.Management
20 ( MxEvent(..)
21 ( MxEvent(..)
21 )
22 )
23+import qualified Control.Exception as IO (bracket)
24+import Data.List (isPrefixOf, isSuffixOf)
22
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
26
31
27import Test.Framework
32import Test.Framework
@@ -29,6 +34,10 @@ import Test.Framework
29 , testGroup
34 , testGroup
30 )
35 )
31import 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)
40+
32
41
33testSpawnTracing :: TestResult Bool -> Process ()
42testSpawnTracing :: TestResult Bool -> Process ()
34testSpawnTracing result = do
43testSpawnTracing result = do
@@ -116,9 +125,11 @@ testTraceSending result = do
116 (\ev ->
125 (\ev ->
117 case ev of
126 case ev of
118 (MxSent to from msg) -> do
127 (MxSent to from msg) -> do
119− (Just s) <- unwrapMessage msg :: Process (Maybe String)
128+ mS <- unwrapMessage msg :: Process (Maybe String)
120− stash res (to == pid && from == self && s == "hello there")
129+ case mS of
121− stash res (to == pid && from == self)
130+ (Just s) -> do stash res (to == pid && from == self && s == "hello there")
131+ stash res (to == pid && from == self)
132+ _ -> die "failed state invariant, message type unmatched..."
122 _ ->
133 _ ->
123 return ()) $ do
134 return ()) $ do
124 send pid "hello there"
135 send pid "hello there"
@@ -237,12 +248,8 @@ testRemoteTraceRelay TestTransport{..} result =
237 -- 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
238 -- that the tracer (on node1) has seen the expected
249 -- that the tracer (on node1) has seen the expected
239 -- MxSpawned message, at which point we're finished
250 -- MxSpawned message, at which point we're finished
240− (Just log') <- whereis "logger"
241− pid <- liftIO $ forkProcess node2 $ do
242− logRelay <- spawnLocal $ relay log'
243− reregister "logger" logRelay
244− getSelfNode >>= stash mvNid >> (expect :: Process ())
245
251
252+ pid <- splinchLogger node2 mvNid
246 nid <- liftIO $ takeMVar mvNid
253 nid <- liftIO $ takeMVar mvNid
247 mref <- monitor pid
254 mref <- monitor pid
248 observedPid <- liftIO $ newEmptyMVar
255 observedPid <- liftIO $ newEmptyMVar
@@ -282,6 +289,96 @@ testRemoteTraceRelay TestTransport{..} result =
282 -- and just to be polite...
289 -- and just to be polite...
283 liftIO $ closeLocalNode node2
290 liftIO $ closeLocalNode node2
284
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+
285tests :: TestTransport -> IO [Test]
382tests :: TestTransport -> IO [Test]
286tests testtrans@TestTransport{..} = do
383tests testtrans@TestTransport{..} = do
287 node1 <- newLocalNode testTransport initRemoteTable
384 node1 <- newLocalNode testTransport initRemoteTable
@@ -323,4 +420,12 @@ tests testtrans@TestTransport{..} = do
323 (synchronisedAssertion
420 (synchronisedAssertion
324 "expected blah"
421 "expected blah"
325 node1 True (testRemoteTraceRelay testtrans) 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+ ]
326 ] ]
431 ] ]
distributed-process-tests/tests/runInMemory.hs−1
@@ -8,7 +8,6 @@ import Network.Transport.Test (TestTransport(..))
8import Network.Transport.InMemory
8import Network.Transport.InMemory
9import Test.Framework (defaultMainWithArgs)
9import Test.Framework (defaultMainWithArgs)
10
10
11−import Control.Concurrent (threadDelay)
12import System.Environment (getArgs)
11import System.Environment (getArgs)
13
12
14main :: IO ()
13main :: IO ()
distributed-process-tests/tests/runTCP.hs+10−3
@@ -10,21 +10,28 @@ import Network.Transport.TCP
10 ( createTransportExposeInternals
10 ( createTransportExposeInternals
11 , TransportInternals(socketBetween)
11 , TransportInternals(socketBetween)
12 , defaultTCPParameters
12 , defaultTCPParameters
13+ , defaultTCPAddr
14+ , TCPParameters(..)
13 )
15 )
14import Test.Framework (defaultMainWithArgs)
16import Test.Framework (defaultMainWithArgs)
15
17
16import Control.Concurrent (threadDelay)
18import Control.Concurrent (threadDelay)
19+import Control.Exception (IOException, try)
17import System.Environment (getArgs)
20import System.Environment (getArgs)
21+import System.IO
18
22
19main :: IO ()
23main :: IO ()
20main = do
24main = do
25+ hSetBuffering stdout LineBuffering
26+ hSetBuffering stderr LineBuffering
21 Right (transport, internals) <-
27 Right (transport, internals) <-
22− createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
28+ createTransportExposeInternals (defaultTCPAddr "127.0.0.1" "8080")
29+ defaultTCPParameters { transportConnectTimeout = Just 3000000 }
23 ts <- tests TestTransport
30 ts <- tests TestTransport
24 { testTransport = transport
31 { testTransport = transport
25 , testBreakConnection = \addr1 addr2 -> do
32 , testBreakConnection = \addr1 addr2 -> do
26− sock <- socketBetween internals addr1 addr2
33+ esock <- try $ socketBetween internals addr1 addr2
27− sClose sock
34+ either (\e -> const (return ()) (e :: IOException)) sClose esock
28 threadDelay 10000
35 threadDelay 10000
29 }
36 }
30 args <- getArgs
37 args <- getArgs
distributed-process.cabal+51−37
@@ -1,12 +1,12 @@
1Name: distributed-process
1Name: distributed-process
2−Version: 0.5.3
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
7Copyright: Well-Typed LLP, Tweag I/O Limited
7Copyright: 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/
12Bug-Reports: https://github.com/haskell-distributed/distributed-process/issues
12Bug-Reports: https://github.com/haskell-distributed/distributed-process/issues
@@ -21,7 +21,7 @@ Description: This is an implementation of Cloud Haskell, as described in
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 GHC==7.6.2
24+Tested-With: GHC==7.10.3 GHC==8.0.2 GHC==8.2.2 GHC==8.4.4
25Category: Control
25Category: Control
26extra-source-files: ChangeLog
26extra-source-files: ChangeLog
27
27
@@ -41,23 +41,26 @@ flag old-locale
41 default: False
41 default: False
42
42
43Library
43Library
44− Build-Depends: base >= 4.4 && < 5,
44+ Build-Depends: base >= 4.6 && < 5,
45− binary >= 0.6.3 && < 0.8,
45+ binary >= 0.6.3 && < 0.10,
46 hashable >= 1.2.0.5 && < 1.3,
46 hashable >= 1.2.0.5 && < 1.3,
47− network-transport >= 0.4.1.0 && < 0.5,
47+ network-transport >= 0.4.1.0 && < 0.6,
48− stm >= 2.4 && < 2.5,
48+ stm >= 2.4 && < 2.6,
49− transformers >= 0.2 && < 0.5,
49+ transformers >= 0.2 && < 0.6,
50 mtl >= 2.0 && < 2.4,
50 mtl >= 2.0 && < 2.4,
51 data-accessor >= 0.2 && < 0.3,
51 data-accessor >= 0.2 && < 0.3,
52 bytestring >= 0.9 && < 0.11,
52 bytestring >= 0.9 && < 0.11,
53 random >= 1.0 && < 1.2,
53 random >= 1.0 && < 1.2,
54− ghc-prim >= 0.2 && < 0.5,
55 distributed-static >= 0.2 && < 0.4,
54 distributed-static >= 0.2 && < 0.4,
56− rank1dynamic >= 0.1 && < 0.4,
55+ rank1dynamic >= 0.1 && < 0.5,
57− syb >= 0.3 && < 0.7
56+ syb >= 0.3 && < 0.8,
57+ exceptions >= 0.5,
58+ containers >= 0.5 && < 0.7,
59+ deepseq >= 1.3.0.1 && < 1.6
58 Exposed-modules: Control.Distributed.Process,
60 Exposed-modules: Control.Distributed.Process,
59 Control.Distributed.Process.Closure,
61 Control.Distributed.Process.Closure,
60 Control.Distributed.Process.Debug,
62 Control.Distributed.Process.Debug,
63+ Control.Distributed.Process.Internal.BiMultiMap,
61 Control.Distributed.Process.Internal.Closure.BuiltIn,
64 Control.Distributed.Process.Internal.Closure.BuiltIn,
62 Control.Distributed.Process.Internal.Closure.Explicit,
65 Control.Distributed.Process.Internal.Closure.Explicit,
63 Control.Distributed.Process.Internal.CQueue,
66 Control.Distributed.Process.Internal.CQueue,
@@ -75,7 +78,6 @@ Library
75 Control.Distributed.Process.UnsafePrimitives
78 Control.Distributed.Process.UnsafePrimitives
76 Control.Distributed.Process.Management.Internal.Agent,
79 Control.Distributed.Process.Management.Internal.Agent,
77 Control.Distributed.Process.Management.Internal.Bus,
80 Control.Distributed.Process.Management.Internal.Bus,
78− Control.Distributed.Process.Management.Internal.Table,
79 Control.Distributed.Process.Management.Internal.Types,
81 Control.Distributed.Process.Management.Internal.Types,
80 Control.Distributed.Process.Management.Internal.Trace.Primitives,
82 Control.Distributed.Process.Management.Internal.Trace.Primitives,
81 Control.Distributed.Process.Management.Internal.Trace.Remote,
83 Control.Distributed.Process.Management.Internal.Trace.Remote,
@@ -83,21 +85,33 @@ Library
83 Control.Distributed.Process.Management.Internal.Trace.Tracer
85 Control.Distributed.Process.Management.Internal.Trace.Tracer
84 ghc-options: -Wall
86 ghc-options: -Wall
85 HS-Source-Dirs: src
87 HS-Source-Dirs: src
86− if impl(ghc <= 7.4.2)
88+ other-extensions: BangPatterns
87− Build-Depends: containers >= 0.4 && < 0.5,
89+ CPP
88− deepseq == 1.3.0.0
90+ DeriveDataTypeable
89− else
91+ DeriveFunctor
90− Build-Depends: containers >= 0.4 && < 0.6,
92+ DeriveGeneric
91− deepseq >= 1.3.0.1 && < 1.6
93+ ExistentialQuantification
94+ FlexibleInstances
95+ GADTs
96+ GeneralizedNewtypeDeriving
97+ KindSignatures
98+ MagicHash
99+ PatternGuards
100+ RankNTypes
101+ RecordWildCards
102+ ScopedTypeVariables
103+ StandaloneDeriving
104+ TypeFamilies
105+ TypeSynonymInstances
106+ UnboxedTuples
107+ UndecidableInstances
92 if flag(old-locale)
108 if flag(old-locale)
93 Build-Depends: time < 1.5, old-locale >= 1.0 && <1.1
109 Build-Depends: time < 1.5, old-locale >= 1.0 && <1.1
94 else
110 else
95 Build-Depends: time >= 1.5
111 Build-Depends: time >= 1.5
96 if flag(th)
112 if flag(th)
97− if impl(ghc <= 7.4.2)
113+ other-extensions: TemplateHaskell
98− Build-Depends: template-haskell >= 2.7 && < 2.8
114+ Build-Depends: template-haskell >= 2.6
99− else
100− Build-Depends: template-haskell >= 2.6 && < 2.11
101 Exposed-modules: Control.Distributed.Process.Internal.Closure.TH
115 Exposed-modules: Control.Distributed.Process.Internal.Closure.TH
102 CPP-Options: -DTemplateHaskellSupport
116 CPP-Options: -DTemplateHaskellSupport
103
117
@@ -105,50 +119,50 @@ Library
105
119
106benchmark distributed-process-throughput
120benchmark distributed-process-throughput
107 Type: exitcode-stdio-1.0
121 Type: exitcode-stdio-1.0
108− Build-Depends: base >= 4.4 && < 5,
122+ Build-Depends: base >= 4.6 && < 5,
109 distributed-process,
123 distributed-process,
110− network-transport-tcp >= 0.3 && < 0.5,
124+ network-transport-tcp >= 0.3 && < 0.7,
111 bytestring >= 0.9 && < 0.11,
125 bytestring >= 0.9 && < 0.11,
112− binary >= 0.6.3 && < 0.8
126+ binary >= 0.6.3 && < 0.10
113 Main-Is: benchmarks/Throughput.hs
127 Main-Is: benchmarks/Throughput.hs
114 ghc-options: -Wall
128 ghc-options: -Wall
115
129
116benchmark distributed-process-latency
130benchmark distributed-process-latency
117 Type: exitcode-stdio-1.0
131 Type: exitcode-stdio-1.0
118− Build-Depends: base >= 4.4 && < 5,
132+ Build-Depends: base >= 4.6 && < 5,
119 distributed-process,
133 distributed-process,
120− network-transport-tcp >= 0.3 && < 0.5,
134+ network-transport-tcp >= 0.3 && < 0.7,
121 bytestring >= 0.9 && < 0.11,
135 bytestring >= 0.9 && < 0.11,
122− binary >= 0.6.3 && < 0.8
136+ binary >= 0.6.3 && < 0.10
123 Main-Is: benchmarks/Latency.hs
137 Main-Is: benchmarks/Latency.hs
124 ghc-options: -Wall
138 ghc-options: -Wall
125
139
126benchmark distributed-process-channels
140benchmark distributed-process-channels
127 Type: exitcode-stdio-1.0
141 Type: exitcode-stdio-1.0
128− Build-Depends: base >= 4.4 && < 5,
142+ Build-Depends: base >= 4.6 && < 5,
129 distributed-process,
143 distributed-process,
130− network-transport-tcp >= 0.3 && < 0.5,
144+ network-transport-tcp >= 0.3 && < 0.7,
131 bytestring >= 0.9 && < 0.11,
145 bytestring >= 0.9 && < 0.11,
132− binary >= 0.6.3 && < 0.8
146+ binary >= 0.6.3 && < 0.10
133 Main-Is: benchmarks/Channels.hs
147 Main-Is: benchmarks/Channels.hs
134 ghc-options: -Wall
148 ghc-options: -Wall
135
149
136benchmark distributed-process-spawns
150benchmark distributed-process-spawns
137 Type: exitcode-stdio-1.0
151 Type: exitcode-stdio-1.0
138− Build-Depends: base >= 4.4 && < 5,
152+ Build-Depends: base >= 4.6 && < 5,
139 distributed-process,
153 distributed-process,
140− network-transport-tcp >= 0.3 && < 0.5,
154+ network-transport-tcp >= 0.3 && < 0.7,
141 bytestring >= 0.9 && < 0.11,
155 bytestring >= 0.9 && < 0.11,
142− binary >= 0.6.3 && < 0.8
156+ binary >= 0.6.3 && < 0.10
143 Main-Is: benchmarks/Spawns.hs
157 Main-Is: benchmarks/Spawns.hs
144 ghc-options: -Wall
158 ghc-options: -Wall
145
159
146benchmark distributed-process-ring
160benchmark distributed-process-ring
147 Type: exitcode-stdio-1.0
161 Type: exitcode-stdio-1.0
148− Build-Depends: base >= 4.4 && < 5,
162+ Build-Depends: base >= 4.6 && < 5,
149 distributed-process,
163 distributed-process,
150− network-transport-tcp >= 0.3 && < 0.5,
164+ network-transport-tcp >= 0.3 && < 0.7,
151 bytestring >= 0.9 && < 0.11,
165 bytestring >= 0.9 && < 0.11,
152− binary >= 0.6.3 && < 0.8
166+ binary >= 0.6.3 && < 0.10
153 Main-Is: benchmarks/ProcessRing.hs
167 Main-Is: benchmarks/ProcessRing.hs
154 ghc-options: -Wall -threaded -O2 -rtsopts
168 ghc-options: -Wall -threaded -O2 -rtsopts
src/Control/Distributed/Process.hs+42−12
@@ -40,8 +40,10 @@ module Control.Distributed.Process
40 , mergePortsRR
40 , mergePortsRR
41 -- * Unsafe messaging variants
41 -- * Unsafe messaging variants
42 , unsafeSend
42 , unsafeSend
43+ , unsafeUSend
43 , unsafeSendChan
44 , unsafeSendChan
44 , unsafeNSend
45 , unsafeNSend
46+ , unsafeNSendRemote
45 , unsafeWrapMessage
47 , unsafeWrapMessage
46 -- * Advanced messaging
48 -- * Advanced messaging
47 , Match
49 , Match
@@ -100,6 +102,7 @@ module Control.Distributed.Process
100 , monitorPort
102 , monitorPort
101 , unmonitor
103 , unmonitor
102 , withMonitor
104 , withMonitor
105+ , withMonitor_
103 , MonitorRef -- opaque
106 , MonitorRef -- opaque
104 , ProcessLinkException(..)
107 , ProcessLinkException(..)
105 , NodeLinkException(..)
108 , NodeLinkException(..)
@@ -157,13 +160,10 @@ module Control.Distributed.Process
157 , reconnectPort
160 , reconnectPort
158 ) where
161 ) where
159
162
160−#if ! MIN_VERSION_base(4,6,0)
161−import Prelude hiding (catch)
162−#endif
163−
164import Control.Monad.IO.Class (liftIO)
163import Control.Monad.IO.Class (liftIO)
165−import Control.Applicative ((<$>))
164+import Control.Applicative
166import Control.Monad.Reader (ask)
165import Control.Monad.Reader (ask)
166+import Control.Concurrent (killThread)
167import Control.Concurrent.MVar
167import Control.Concurrent.MVar
168 ( MVar
168 ( MVar
169 , newEmptyMVar
169 , newEmptyMVar
@@ -198,6 +198,7 @@ import Control.Distributed.Process.Internal.Types
198 , RegisterReply(..)
198 , RegisterReply(..)
199 , LocalProcess(processNode)
199 , LocalProcess(processNode)
200 , Message
200 , Message
201+ , localProcessWithId
201 )
202 )
202import Control.Distributed.Process.Serializable (Serializable)
203import Control.Distributed.Process.Serializable (Serializable)
203import Control.Distributed.Process.Internal.Primitives
204import Control.Distributed.Process.Internal.Primitives
@@ -212,8 +213,10 @@ import Control.Distributed.Process.Internal.Primitives
212 , mergePortsBiased
213 , mergePortsBiased
213 , mergePortsRR
214 , mergePortsRR
214 , unsafeSend
215 , unsafeSend
216+ , unsafeUSend
215 , unsafeSendChan
217 , unsafeSendChan
216 , unsafeNSend
218 , unsafeNSend
219+ , unsafeNSendRemote
217 -- Advanced messaging
220 -- Advanced messaging
218 , Match
221 , Match
219 , receiveWait
222 , receiveWait
@@ -267,6 +270,7 @@ import Control.Distributed.Process.Internal.Primitives
267 , monitorPort
270 , monitorPort
268 , unmonitor
271 , unmonitor
269 , withMonitor
272 , withMonitor
273+ , withMonitor_
270 -- Logging
274 -- Logging
271 , say
275 , say
272 -- Registry
276 -- Registry
@@ -303,6 +307,10 @@ import Control.Distributed.Process.Internal.Primitives
303 , reconnectPort
307 , reconnectPort
304 )
308 )
305import Control.Distributed.Process.Node (forkProcess)
309import Control.Distributed.Process.Node (forkProcess)
310+import Control.Distributed.Process.Internal.Types
311+ ( processThread
312+ , withValidLocalState
313+ )
306import Control.Distributed.Process.Internal.Spawn
314import Control.Distributed.Process.Internal.Spawn
307 ( -- Spawning Processes/Channels
315 ( -- Spawning Processes/Channels
308 spawn
316 spawn
@@ -312,7 +320,17 @@ import Control.Distributed.Process.Internal.Spawn
312 , spawnSupervised
320 , spawnSupervised
313 , call
321 , call
314 )
322 )
315−import Control.Exception (SomeException, throw)
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+
316
334
317-- INTERNAL NOTES
335-- INTERNAL NOTES
318--
336--
@@ -409,9 +427,21 @@ spawnChannelLocal proc = do
409-- or duplicate messages sent to the isolated process after it exits.
427-- or duplicate messages sent to the isolated process after it exits.
410-- Silently dropping messages may not always be the best approach.
428-- Silently dropping messages may not always be the best approach.
411callLocal :: Process a -> Process a
429callLocal :: Process a -> Process a
412−callLocal proc = mask $ \release -> do
430+callLocal proc = Catch.mask $ \release -> do
413− mv <- liftIO newEmptyMVar :: Process (MVar (Either SomeException a))
431+ mv <- liftIO newEmptyMVar :: Process (MVar (Either Catch.SomeException a))
414− child <- spawnLocal $ try (release proc) >>= liftIO . putMVar mv
432+ child <- spawnLocal $ Catch.try (release proc) >>= liftIO . putMVar mv
415− rs <- liftIO (takeMVar mv) `onException`
433+ lproc <- ask
416− (kill child "exception in parent process" >> liftIO (takeMVar mv))
434+ liftIO $ do
417− either throw return rs
435+ rs <- Exception.onException (takeMVar mv) $ Catch.uninterruptibleMask_ $
436+ -- Exceptions need to be prevented from interrupting the clean up or
437+ -- the original exception which caused entering the handler could be
438+ -- forgotten. For instance, this could have a problematic effect
439+ -- when the original exception was meant to kill the thread and the
440+ -- second exception doesn't (like the exception thrown by
441+ -- 'System.Timeout.timeout').
442+ do mchildThreadId <- withValidLocalState (processNode lproc) $
443+ \vst -> return $ fmap processThread $
444+ vst ^. localProcessWithId (processLocalId child)
445+ forM_ mchildThreadId killThread
446+ takeMVar mv
447+ either Catch.throwM return rs
src/Control/Distributed/Process/Debug.hs+10−14
@@ -83,13 +83,13 @@
83-- environment variable accepts the following flags, which enable tracing specific
83-- environment variable accepts the following flags, which enable tracing specific
84-- event types:
84-- event types:
85--
85--
86−-- p = trace the spawning of new processes
86+-- * @p@ = trace the spawning of new processes
87−-- d = trace the death of processes
87+-- * @d@ = trace the death of processes
88−-- n = trace registration of names (i.e., named processes)
88+-- * @n@ = trace registration of names (i.e., named processes)
89−-- u = trace un-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
90+-- * @s@ = trace the sending of messages to other processes
91−-- r = trace the receipt of messages from other processes
91+-- * @r@ = trace the receipt of messages from other processes
92−-- l = trace node up/down events
92+-- * @l@ = trace node up/down events
93--
93--
94-- Users of the /simplelocalnet/ Cloud Haskell backend should also note that
94-- Users of the /simplelocalnet/ Cloud Haskell backend should also note that
95-- 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
@@ -141,17 +141,14 @@ module Control.Distributed.Process.Debug
141 )
141 )
142 where
142 where
143
143
144−import Control.Applicative ((<$>))
144+import Control.Applicative
145import Control.Distributed.Process.Internal.Primitives
145import Control.Distributed.Process.Internal.Primitives
146 ( proxy
146 ( proxy
147− , finally
148 , die
147 , die
149 , whereis
148 , whereis
150 , send
149 , send
151 , receiveWait
150 , receiveWait
152 , matchIf
151 , matchIf
153− , finally
154− , try
155 , monitor
152 , monitor
156 )
153 )
157import Control.Distributed.Process.Internal.Types
154import Control.Distributed.Process.Internal.Types
@@ -196,12 +193,11 @@ import Control.Exception (SomeException)
196
193
197import Control.Monad.IO.Class (liftIO)
194import Control.Monad.IO.Class (liftIO)
198import Control.Monad.Reader (ask)
195import Control.Monad.Reader (ask)
196+import Control.Monad.Catch (finally, try)
199
197
200import Data.Binary()
198import Data.Binary()
201
199
202−#if ! MIN_VERSION_base(4,6,0)
200+import Prelude
203−import Prelude hiding (catch)
204−#endif
205
201
206--------------------------------------------------------------------------------
202--------------------------------------------------------------------------------
207-- Debugging/Tracing API --
203-- Debugging/Tracing API --
src/Control/Distributed/Process/Internal/BiMultiMap.hsnew file+135
@@ -0,0 +1,135 @@
1+-- | This is an implementation of bidirectional multimaps.
2+module Control.Distributed.Process.Internal.BiMultiMap
3+ ( BiMultiMap
4+ , empty
5+ , singleton
6+ , size
7+ , insert
8+ , lookupBy1st
9+ , lookupBy2nd
10+ , delete
11+ , deleteAllBy1st
12+ , deleteAllBy2nd
13+ , partitionWithKeyBy1st
14+ , partitionWithKeyBy2nd
15+ , flip
16+ ) where
17+
18+import Data.List (foldl')
19+import Data.Map.Strict (Map)
20+import qualified Data.Map.Strict as Map
21+import Data.Set (Set)
22+import qualified Data.Set as Set
23+import Prelude hiding (flip, lookup)
24+
25+-- | A bidirectional multimaps @BiMultiMap a b v@ is a set of triplets of type
26+-- @(a, b, v)@.
27+--
28+-- It is possible to lookup values by using either @a@ or @b@ as keys.
29+--
30+data BiMultiMap a b v = BiMultiMap !(Map a (Set (b, v))) !(Map b (Set (a, v)))
31+
32+-- The bidirectional multimap is implemented with a pair of multimaps.
33+--
34+-- Each multimap represents a set of triples, and one invariant is that both
35+-- multimaps should represent exactly the same set of triples.
36+--
37+-- Each of the multimaps, however, uses a different component of the triplets
38+-- as key. This allows to do efficient deletions by any of the two components.
39+
40+-- | The empty bidirectional multimap.
41+empty :: BiMultiMap a b v
42+empty = BiMultiMap Map.empty Map.empty
43+
44+-- | A bidirectional multimap containing a single triplet.
45+singleton :: (Ord a, Ord b, Ord v) => a -> b -> v -> BiMultiMap a b v
46+singleton a b v = insert a b v empty
47+
48+-- | Yields the amount of triplets in the multimap.
49+size :: BiMultiMap a b v -> Int
50+size (BiMultiMap m _) = foldl' (+) 0 $ map Set.size $ Map.elems m
51+
52+-- | Inserts a triplet in the multimap.
53+insert :: (Ord a, Ord b, Ord v)
54+ => a -> b -> v -> BiMultiMap a b v -> BiMultiMap a b v
55+insert a b v (BiMultiMap m r) =
56+ BiMultiMap (Map.insertWith (\_new old -> Set.insert (b, v) old)
57+ a
58+ (Set.singleton (b, v))
59+ m)
60+ (Map.insertWith (\_new old -> Set.insert (a, v) old)
61+ b
62+ (Set.singleton (a, v))
63+ r)
64+
65+-- | Looks up all the triplets whose first component is the given value.
66+lookupBy1st :: Ord a => a -> BiMultiMap a b v -> Set (b, v)
67+lookupBy1st a (BiMultiMap m _) = maybe Set.empty id $ Map.lookup a m
68+
69+-- | Looks up all the triplets whose second component is the given value.
70+lookupBy2nd :: Ord b => b -> BiMultiMap a b v -> Set (a, v)
71+lookupBy2nd b = lookupBy1st b . flip
72+
73+-- | Deletes a triplet. It yields the original multimap if the triplet is
74+-- not present.
75+delete :: (Ord a, Ord b, Ord v)
76+ => a -> b -> v -> BiMultiMap a b v -> BiMultiMap a b v
77+delete a b v (BiMultiMap m r) =
78+ let m' = Map.update (nothingWhen Set.null . Set.delete (b, v)) a m
79+ r' = Map.update (nothingWhen Set.null . Set.delete (a, v)) b r
80+ in BiMultiMap m' r'
81+
82+-- | Deletes all triplets whose first component is the given value.
83+deleteAllBy1st :: (Ord a, Ord b, Ord v) => a -> BiMultiMap a b v -> BiMultiMap a b v
84+deleteAllBy1st a (BiMultiMap m r) =
85+ let (mm, m') = Map.updateLookupWithKey (\_ _ -> Nothing) a m
86+ r' = case mm of
87+ Nothing -> r
88+ Just mb -> reverseDelete a (Set.toList mb) r
89+ in BiMultiMap m' r'
90+
91+-- | Like 'deleteAllBy1st' but deletes by the second component of the triplets.
92+deleteAllBy2nd :: (Ord a, Ord b, Ord v)
93+ => b -> BiMultiMap a b v -> BiMultiMap a b v
94+deleteAllBy2nd b = flip . deleteAllBy1st b . flip
95+
96+-- | Yields the triplets satisfying the given predicate, and a multimap
97+-- with all this triplets removed.
98+partitionWithKeyBy1st :: (Ord a, Ord b, Ord v)
99+ => (a -> Set (b, v) -> Bool) -> BiMultiMap a b v
100+ -> (Map a (Set (b, v)), BiMultiMap a b v)
101+partitionWithKeyBy1st p (BiMultiMap m r) =
102+ let (m0, m1) = Map.partitionWithKey p m
103+ r1 = foldl' (\rr (a, mb) -> reverseDelete a (Set.toList mb) rr) r $
104+ Map.toList m0
105+ in (m0, BiMultiMap m1 r1)
106+
107+-- | Like 'partitionWithKeyBy1st' but the predicates takes the second component
108+-- of the triplets as first argument.
109+partitionWithKeyBy2nd :: (Ord a, Ord b, Ord v)
110+ => (b -> Set (a, v) -> Bool) -> BiMultiMap a b v
111+ -> (Map b (Set (a, v)), BiMultiMap a b v)
112+partitionWithKeyBy2nd p b = let (m, b') = partitionWithKeyBy1st p $ flip b
113+ in (m, flip b')
114+
115+-- | Exchange the first and the second components of all triplets.
116+flip :: BiMultiMap a b v -> BiMultiMap b a v
117+flip (BiMultiMap m r) = BiMultiMap r m
118+
119+-- Internal functions
120+
121+-- | @reverseDelete a bs m@ removes from @m@ all the triplets wich have @a@ as
122+-- first component and second and third components in @bs@.
123+--
124+-- The @m@ map is in reversed form, meaning that the second component of the
125+-- triplets is used as key.
126+reverseDelete :: (Ord a, Ord b, Ord v)
127+ => a -> [(b, v)] -> Map b (Set (a, v)) -> Map b (Set (a, v))
128+reverseDelete a bs r = foldl' (\rr (b, v) -> Map.update (rmb v) b rr) r bs
129+ where
130+ rmb v = nothingWhen Set.null . Set.delete (a, v)
131+
132+-- | @nothingWhen p a@ is @Just a@ when @a@ satisfies predicate @p@.
133+-- Yields @Nothing@ otherwise.
134+nothingWhen :: (a -> Bool) -> a -> Maybe a
135+nothingWhen p a = if p a then Nothing else Just a
src/Control/Distributed/Process/Internal/CQueue.hs+31−2
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
1{-# LANGUAGE DeriveFunctor #-}
2{-# LANGUAGE DeriveFunctor #-}
2{-# LANGUAGE BangPatterns #-}
3{-# LANGUAGE BangPatterns #-}
3{-# LANGUAGE MagicHash, UnboxedTuples, PatternGuards, ScopedTypeVariables, RankNTypes #-}
4{-# LANGUAGE MagicHash, UnboxedTuples, PatternGuards, ScopedTypeVariables, RankNTypes #-}
@@ -46,8 +47,8 @@ import Control.Distributed.Process.Internal.StrictList
46import Data.Maybe (fromJust)
47import Data.Maybe (fromJust)
47import Data.Traversable (traverse)
48import Data.Traversable (traverse)
48import GHC.MVar (MVar(MVar))
49import GHC.MVar (MVar(MVar))
49−import GHC.IO (IO(IO))
50+import GHC.IO (IO(IO), unIO)
50−import GHC.Prim (mkWeak#)
51+import GHC.Exts (mkWeak#)
51import GHC.Weak (Weak(Weak))
52import GHC.Weak (Weak(Weak))
52
53
53-- 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
@@ -75,13 +76,25 @@ data BlockSpec =
75 | Blocking
76 | Blocking
76 | Timeout Int
77 | Timeout Int
77
78
79+-- Match operations
80+--
81+-- They can be either a message match or a channel match.
78data MatchOn m a
82data MatchOn m a
79 = MatchMsg (m -> Maybe a)
83 = MatchMsg (m -> Maybe a)
80 | MatchChan (STM a)
84 | MatchChan (STM a)
81 deriving (Functor)
85 deriving (Functor)
82
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.
83type MatchChunks m a = [Either [m -> Maybe a] [STM a]]
92type MatchChunks m a = [Either [m -> Maybe a] [STM a]]
84
93
94+-- Splits a list of matches into chunks.
95+--
96+-- > concatMap (either (map MatchMsg) (map MatchChan)) . chunkMatches == id
97+--
85chunkMatches :: [MatchOn m a] -> MatchChunks m a
98chunkMatches :: [MatchOn m a] -> MatchChunks m a
86chunkMatches [] = []
99chunkMatches [] = []
87chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest
100chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest
@@ -89,6 +102,7 @@ chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest
89chunkMatches (MatchChan r : ms) = Right (r : chk) : chunkMatches rest
102chunkMatches (MatchChan r : ms) = Right (r : chk) : chunkMatches rest
90 where (chk, rest) = spanMatchChan ms
103 where (chk, rest) = spanMatchChan ms
91
104
105+-- | @spanMatchMsg = first (map (\(MatchMsg x) -> x)) . span isMatchMsg@
92spanMatchMsg :: [MatchOn m a] -> ([m -> Maybe a], [MatchOn m a])
106spanMatchMsg :: [MatchOn m a] -> ([m -> Maybe a], [MatchOn m a])
93spanMatchMsg [] = ([],[])
107spanMatchMsg [] = ([],[])
94spanMatchMsg (m : ms)
108spanMatchMsg (m : ms)
@@ -96,6 +110,7 @@ spanMatchMsg (m : ms)
96 | otherwise = ([], m:ms)
110 | otherwise = ([], m:ms)
97 where !(msgs,rest) = spanMatchMsg ms
111 where !(msgs,rest) = spanMatchMsg ms
98
112
113+-- | @spanMatchMsg = first (map (\(MatchChan x) -> x)) . span isMatchChan@
99spanMatchChan :: [MatchOn m a] -> ([STM a], [MatchOn m a])
114spanMatchChan :: [MatchOn m a] -> ([STM a], [MatchOn m a])
100spanMatchChan [] = ([],[])
115spanMatchChan [] = ([],[])
101spanMatchChan (m : ms)
116spanMatchChan (m : ms)
@@ -127,6 +142,7 @@ dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJus
127 -- Decrement counter is smth is returned from the queue,
142 -- Decrement counter is smth is returned from the queue,
128 -- this is safe to use as method is called under a mask
143 -- this is safe to use as method is called under a mask
129 -- and there is no 'unmasked' operation inside
144 -- and there is no 'unmasked' operation inside
145+ decrementJust :: IO (Maybe (Either a a)) -> IO (Maybe a)
130 decrementJust f =
146 decrementJust f =
131 traverse (either return (\x -> decrement >> return x)) =<< f
147 traverse (either return (\x -> decrement >> return x)) =<< f
132 decrement = atomically $ modifyTVar' size pred
148 decrement = atomically $ modifyTVar' size pred
@@ -143,6 +159,10 @@ dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJus
143 arr' <- grabNew arr
159 arr' <- grabNew arr
144 goCheck chunks arr'
160 goCheck chunks arr'
145
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))
146 waitChans ports on_block =
166 waitChans ports on_block =
147 foldr orElse on_block (map (fmap (Just . Left)) ports)
167 foldr orElse on_block (map (fmap (Just . Left)) ports)
148
168
@@ -151,6 +171,10 @@ dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJus
151 -- mailbox. For channel matches, we do a non-blocking check at
171 -- mailbox. For channel matches, we do a non-blocking check at
152 -- this point.
172 -- this point.
153 --
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+ --
154 goCheck :: MatchChunks m a
178 goCheck :: MatchChunks m a
155 -> StrictList m -- messages to check, in this order
179 -> StrictList m -- messages to check, in this order
156 -> IO (Maybe (Either a a))
180 -> IO (Maybe (Either a a))
@@ -249,6 +273,7 @@ dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJus
249 checkArrived :: [m -> Maybe a] -> StrictList m -> (StrictList m, Maybe a)
273 checkArrived :: [m -> Maybe a] -> StrictList m -> (StrictList m, Maybe a)
250 checkArrived matches list = go list Nil
274 checkArrived matches list = go list Nil
251 where
275 where
276+ -- @go xs ys@ searches for a message match in @append xs ys@
252 go Nil Nil = (Nil, Nothing)
277 go Nil Nil = (Nil, Nothing)
253 go Nil r = go r Nil
278 go Nil r = go r Nil
254 go (Append xs ys) tl = go xs (append ys tl)
279 go (Append xs ys) tl = go xs (append ys tl)
@@ -265,7 +290,11 @@ dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJus
265-- | Weak reference to a CQueue
290-- | Weak reference to a CQueue
266mkWeakCQueue :: CQueue a -> IO () -> IO (Weak (CQueue a))
291mkWeakCQueue :: CQueue a -> IO () -> IO (Weak (CQueue a))
267mkWeakCQueue m@(CQueue (StrictMVar (MVar m#)) _ _) f = IO $ \s ->
292mkWeakCQueue 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
268 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
269
298
270queueSize :: CQueue a -> IO Int
299queueSize :: CQueue a -> IO Int
271queueSize (CQueue _ _ size) = readTVarIO size
300queueSize (CQueue _ _ size) = readTVarIO size
src/Control/Distributed/Process/Internal/Closure/Explicit.hs+1−2
@@ -6,7 +6,6 @@
6 , UndecidableInstances
6 , UndecidableInstances
7 , KindSignatures
7 , KindSignatures
8 , GADTs
8 , GADTs
9− , OverlappingInstances
10 , EmptyDataDecls
9 , EmptyDataDecls
11 , DeriveDataTypeable #-}
10 , DeriveDataTypeable #-}
12module Control.Distributed.Process.Internal.Closure.Explicit
11module Control.Distributed.Process.Internal.Closure.Explicit
@@ -109,7 +108,7 @@ instance Binary EndOfTuple where
109class Curry a b | a -> b where
108class Curry a b | a -> b where
110 curryFun :: a -> b
109 curryFun :: a -> b
111
110
112−instance Curry ((a,EndOfTuple) -> b) (a -> b) where
111+instance Curry ((a, EndOfTuple) -> b) (a -> b) where
113 curryFun f = \x -> f (x,undefined)
112 curryFun f = \x -> f (x,undefined)
114
113
115instance Curry (b -> c) r => Curry ((a,b) -> c) (a -> r) where
114instance Curry (b -> c) r => Curry ((a,b) -> c) (a -> r) where
src/Control/Distributed/Process/Internal/Closure/TH.hs+15−7
@@ -29,11 +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−#if ! MIN_VERSION_template_haskell(2,10,0)
33 , Pred
32 , Pred
33+#if MIN_VERSION_template_haskell(2,10,0)
34+ , conT
35+ , appT
36+#else
37+ , classP
34#endif
38#endif
35 , varT
39 , varT
36− , classP
37 -- Lifted constructors
40 -- Lifted constructors
38 -- .. Literals
41 -- .. Literals
39 , stringL
42 , stringL
@@ -71,10 +74,6 @@ import Control.Distributed.Process.Serializable
71 )
74 )
72import Control.Distributed.Process.Internal.Closure.BuiltIn (staticDecode)
75import Control.Distributed.Process.Internal.Closure.BuiltIn (staticDecode)
73
76
74−#if MIN_VERSION_template_haskell(2,10,0)
75−type Pred = Type
76−#endif
77−
78--------------------------------------------------------------------------------
77--------------------------------------------------------------------------------
79-- User-level API --
78-- User-level API --
80--------------------------------------------------------------------------------
79--------------------------------------------------------------------------------
@@ -261,7 +260,12 @@ generateStatic n xs typ = do
261 ]
260 ]
262 where
261 where
263 typeable :: TyVarBndr -> Q Pred
262 typeable :: TyVarBndr -> Q Pred
264− typeable tv = classP (mkName "Typeable") [varT (tyVarBndrName tv)]
263+ typeable tv =
264+#if MIN_VERSION_template_haskell(2,10,0)
265+ conT (mkName "Typeable") `appT` varT (tyVarBndrName tv)
266+#else
267+ classP (mkName "Typeable") [varT (tyVarBndrName tv)]
268+#endif
265
269
266-- | Generate a serialization dictionary with name 'n' for type 'typ'
270-- | Generate a serialization dictionary with name 'n' for type 'typ'
267generateDict :: Name -> Type -> Q [Dec]
271generateDict :: Name -> Type -> Q [Dec]
@@ -299,7 +303,11 @@ getType :: Name -> Q (Name, Type)
299getType name = do
303getType name = do
300 info <- reify name
304 info <- reify name
301 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
302 VarI origName typ _ _ -> return (origName, typ)
309 VarI origName typ _ _ -> return (origName, typ)
310+#endif
303 _ -> fail $ show name ++ " not found"
311 _ -> fail $ show name ++ " not found"
304
312
305-- | 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+2−2
@@ -30,7 +30,7 @@ import qualified Network.Transport as NT
30 , close
30 , close
31 )
31 )
32import Control.Distributed.Process.Internal.Types
32import Control.Distributed.Process.Internal.Types
33− ( LocalNode(localState, localEndPoint, localCtrlChan)
33+ ( LocalNode(localEndPoint, localCtrlChan)
34 , withValidLocalState
34 , withValidLocalState
35 , modifyValidLocalState
35 , modifyValidLocalState
36 , modifyValidLocalState_
36 , modifyValidLocalState_
@@ -201,7 +201,7 @@ sendCtrlMsg mNid signal = do
201 liftIO $ writeChan (localCtrlChan (processNode proc)) $! msg
201 liftIO $ writeChan (localCtrlChan (processNode proc)) $! msg
202 Just nid ->
202 Just nid ->
203 liftIO $ sendBinary (processNode proc)
203 liftIO $ sendBinary (processNode proc)
204− (ProcessIdentifier (processId proc))
204+ (NodeIdentifier (processNodeId $ processId proc))
205 (NodeIdentifier nid)
205 (NodeIdentifier nid)
206 WithImplicitReconnect
206 WithImplicitReconnect
207 msg
207 msg
src/Control/Distributed/Process/Internal/Primitives.hs+179−99
@@ -23,8 +23,10 @@ module Control.Distributed.Process.Internal.Primitives
23 , mergePortsRR
23 , mergePortsRR
24 -- * Unsafe messaging variants
24 -- * Unsafe messaging variants
25 , unsafeSend
25 , unsafeSend
26+ , unsafeUSend
26 , unsafeSendChan
27 , unsafeSendChan
27 , unsafeNSend
28 , unsafeNSend
29+ , unsafeNSendRemote
28 -- * Advanced messaging
30 -- * Advanced messaging
29 , Match
31 , Match
30 , receiveWait
32 , receiveWait
@@ -74,8 +76,11 @@ module Control.Distributed.Process.Internal.Primitives
74 , unlink
76 , unlink
75 , monitor
77 , monitor
76 , unmonitor
78 , unmonitor
79+ , unmonitorAsync
77 , withMonitor
80 , withMonitor
81+ , withMonitor_
78 -- * Logging
82 -- * Logging
83+ , SayMessage(..)
79 , say
84 , say
80 -- * Registry
85 -- * Registry
81 , register
86 , register
@@ -123,8 +128,9 @@ module Control.Distributed.Process.Internal.Primitives
123import Prelude hiding (catch)
128import Prelude hiding (catch)
124#endif
129#endif
125
130
126−import Data.Binary (decode)
131+import Data.Binary (Binary(..), Put, Get, decode)
127−import Data.Time.Clock (getCurrentTime)
132+import Data.Time.Clock (getCurrentTime, UTCTime(..))
133+import Data.Time.Calendar (Day(..))
128import Data.Time.Format (formatTime)
134import Data.Time.Format (formatTime)
129#if MIN_VERSION_time(1,5,0)
135#if MIN_VERSION_time(1,5,0)
130import Data.Time.Format (defaultTimeLocale)
136import Data.Time.Format (defaultTimeLocale)
@@ -132,12 +138,17 @@ import Data.Time.Format (defaultTimeLocale)
132import System.Locale (defaultTimeLocale)
138import System.Locale (defaultTimeLocale)
133#endif
139#endif
134import System.Timeout (timeout)
140import System.Timeout (timeout)
135−import Control.Monad (when)
141+import Control.Monad (when, void)
136import Control.Monad.Reader (ask)
142import Control.Monad.Reader (ask)
137import Control.Monad.IO.Class (liftIO)
143import Control.Monad.IO.Class (liftIO)
138−import Control.Applicative ((<$>))
144+import Control.Monad.Catch
139−import Control.Exception (Exception(..), throw, throwIO, SomeException)
145+ ( Exception
140−import qualified Control.Exception as Ex (catch, mask, mask_, try)
146+ , SomeException
147+ , throwM
148+ , fromException
149+ )
150+import qualified Control.Monad.Catch as Catch
151+import Control.Applicative
141import Control.Distributed.Process.Internal.StrictMVar
152import Control.Distributed.Process.Internal.StrictMVar
142 ( StrictMVar
153 ( StrictMVar
143 , modifyMVar
154 , modifyMVar
@@ -177,6 +188,7 @@ import Control.Distributed.Process.Internal.Types
177 , SpawnRef(..)
188 , SpawnRef(..)
178 , ProcessSignal(..)
189 , ProcessSignal(..)
179 , NodeMonitorNotification(..)
190 , NodeMonitorNotification(..)
191+ , ProcessMonitorNotification(..)
180 , monitorCounter
192 , monitorCounter
181 , spawnCounter
193 , spawnCounter
182 , SendPort(..)
194 , SendPort(..)
@@ -201,7 +213,6 @@ import Control.Distributed.Process.Internal.Types
201 , isEncoded
213 , isEncoded
202 , createMessage
214 , createMessage
203 , createUnencodedMessage
215 , createUnencodedMessage
204− , runLocalProcess
205 , ImplicitReconnect( NoImplicitReconnect)
216 , ImplicitReconnect( NoImplicitReconnect)
206 , LocalProcessState
217 , LocalProcessState
207 , LocalSendPortId
218 , LocalSendPortId
@@ -225,6 +236,7 @@ import Control.Distributed.Process.Internal.WeakTQueue
225 , readTQueue
236 , readTQueue
226 , mkWeakTQueue
237 , mkWeakTQueue
227 )
238 )
239+import Prelude
228
240
229import Unsafe.Coerce
241import Unsafe.Coerce
230
242
@@ -242,17 +254,15 @@ send them msg = do
242 node = processNode proc
254 node = processNode proc
243 nodeId = localNodeId node
255 nodeId = localNodeId node
244 destNode = (processNodeId them) in do
256 destNode = (processNodeId them) in do
245− case destNode == nodeId of
246− True -> sendLocal them msg
247− False -> liftIO $ sendMessage (processNode proc)
248− (ProcessIdentifier (processId proc))
249− (ProcessIdentifier them)
250− NoImplicitReconnect
251− msg
252− -- We do not fire the trace event until after the sending is complete;
253− -- In the remote case, 'sendMessage' can block in the networking stack.
254 liftIO $ traceEvent (localEventBus node)
257 liftIO $ traceEvent (localEventBus node)
255 (MxSent 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
256
266
257-- | /Unsafe/ variant of 'send'. This function makes /no/ attempt to serialize
267-- | /Unsafe/ variant of 'send'. This function makes /no/ attempt to serialize
258-- 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
@@ -272,13 +282,23 @@ unsafeSend = Unsafe.send
272--
282--
273usend :: Serializable a => ProcessId -> a -> Process ()
283usend :: Serializable a => ProcessId -> a -> Process ()
274usend them msg = do
284usend them msg = do
275− here <- getSelfNode
285+ proc <- ask
276 let there = processNodeId them
286 let there = processNodeId them
277− if here == there
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
278 then sendLocal them msg
291 then sendLocal them msg
279 else sendCtrlMsg (Just there) $ UnreliableSend (processLocalId them)
292 else sendCtrlMsg (Just there) $ UnreliableSend (processLocalId them)
280 (createMessage msg)
293 (createMessage msg)
281
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+
282-- | Wait for a message of a specific type
302-- | Wait for a message of a specific type
283expect :: forall a. Serializable a => Process a
303expect :: forall a. Serializable a => Process a
284expect = receiveWait [match return]
304expect = receiveWait [match return]
@@ -287,7 +307,13 @@ expect = receiveWait [match return]
287-- Channels --
307-- Channels --
288--------------------------------------------------------------------------------
308--------------------------------------------------------------------------------
289
309
290−-- | 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+--
291newChan :: Serializable a => Process (SendPort a, ReceivePort a)
317newChan :: Serializable a => Process (SendPort a, ReceivePort a)
292newChan = do
318newChan = do
293 proc <- ask
319 proc <- ask
@@ -315,13 +341,16 @@ newChan = do
315sendChan :: Serializable a => SendPort a -> a -> Process ()
341sendChan :: Serializable a => SendPort a -> a -> Process ()
316sendChan (SendPort cid) msg = do
342sendChan (SendPort cid) msg = do
317 proc <- ask
343 proc <- ask
318− let node = localNodeId (processNode proc)
344+ let node = processNode proc
319− destNode = processNodeId (sendPortProcessId cid) in do
345+ pid = processId proc
320− case destNode == node of
346+ us = localNodeId node
347+ them = processNodeId (sendPortProcessId cid) in do
348+ liftIO $ traceEvent (localEventBus node) (MxSentToPort pid cid $ wrapMessage msg)
349+ case them == us of
321 True -> sendChanLocal cid msg
350 True -> sendChanLocal cid msg
322 False -> do
351 False -> do
323− liftIO $ sendBinary (processNode proc)
352+ liftIO $ sendBinary node
324− (ProcessIdentifier (processId proc))
353+ (ProcessIdentifier pid)
325 (SendPortIdentifier cid)
354 (SendPortIdentifier cid)
326 NoImplicitReconnect
355 NoImplicitReconnect
327 msg
356 msg
@@ -382,8 +411,11 @@ newtype Match b = Match { unMatch :: MatchOn Message (Process b) }
382receiveWait :: [Match b] -> Process b
411receiveWait :: [Match b] -> Process b
383receiveWait ms = do
412receiveWait ms = do
384 queue <- processQueue <$> ask
413 queue <- processQueue <$> ask
385− Just proc <- liftIO $ dequeue queue Blocking (map unMatch ms)
414+ mProc <- liftIO $ dequeue queue Blocking (map unMatch ms)
386− proc
415+ case mProc of
416+ Just proc' -> proc'
417+ Nothing -> die $ "System Invariant Violation: CQueue.hs returned `Nothing` "
418+ ++ "in the absence of a timeout value."
387
419
388-- | Like 'receiveWait' but with a timeout.
420-- | Like 'receiveWait' but with a timeout.
389--
421--
@@ -462,17 +494,14 @@ forward msg them = do
462 us = processId proc
494 us = processId proc
463 nid = localNodeId node
495 nid = localNodeId node
464 destNode = (processNodeId them) in do
496 destNode = (processNodeId them) in do
465− case destNode == nid of
497+ liftIO $ traceEvent (localEventBus node) (MxSent them us msg)
466− True -> sendCtrlMsg Nothing (LocalSend them msg)
498+ if destNode == nid
467− False -> liftIO $ sendPayload (processNode proc)
499+ then sendCtrlMsg Nothing (LocalSend them msg)
468− (ProcessIdentifier (processId proc))
500+ else liftIO $ sendPayload (processNode proc)
469− (ProcessIdentifier them)
501+ (ProcessIdentifier (processId proc))
470− NoImplicitReconnect
502+ (ProcessIdentifier them)
471− (messageToPayload msg)
503+ NoImplicitReconnect
472− -- We do not fire the trace event until after the sending is complete;
504+ (messageToPayload msg)
473− -- In the remote case, 'sendMessage' can block in the networking stack.
474− liftIO $ traceEvent (localEventBus node)
475− (MxSent them us msg)
476
505
477-- | Forward a raw 'Message' to the given 'ProcessId'.
506-- | Forward a raw 'Message' to the given 'ProcessId'.
478--
507--
@@ -486,14 +515,10 @@ uforward msg them = do
486 us = processId proc
515 us = processId proc
487 nid = localNodeId node
516 nid = localNodeId node
488 destNode = (processNodeId them) in do
517 destNode = (processNodeId them) in do
489− case destNode == nid of
518+ liftIO $ traceEvent (localEventBus node) (MxSent them us msg)
490− True -> sendCtrlMsg Nothing (LocalSend them msg)
519+ if destNode == nid
491− False -> sendCtrlMsg (Just destNode) $ UnreliableSend (processLocalId them)
520+ then sendCtrlMsg Nothing (LocalSend them msg)
492− msg
521+ else sendCtrlMsg (Just destNode) $ UnreliableSend (processLocalId them) msg
493− -- We do not fire the trace event until after the sending is complete;
494− -- In the remote case, 'sendCtrlMsg' can block in the networking stack.
495− liftIO $ traceEvent (localEventBus node)
496− (MxSent them us msg)
497
522
498-- | 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
499-- 'Serializable' - like the datum they contain - but also note, deserialising
524-- 'Serializable' - like the datum they contain - but also note, deserialising
@@ -690,7 +715,7 @@ instance Exception ProcessTerminationException
690
715
691-- | Terminate immediately (throws a ProcessTerminationException)
716-- | Terminate immediately (throws a ProcessTerminationException)
692terminate :: Process a
717terminate :: Process a
693−terminate = liftIO $ throwIO ProcessTerminationException
718+terminate = throwM ProcessTerminationException
694
719
695-- [Issue #110]
720-- [Issue #110]
696-- | Die immediately - throws a 'ProcessExitException' with the given @reason@.
721-- | Die immediately - throws a 'ProcessExitException' with the given @reason@.
@@ -700,7 +725,7 @@ die reason = do
700-- 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
701-- passed to another process via the tracing mechanism.
726-- passed to another process via the tracing mechanism.
702 pid <- getSelfPid
727 pid <- getSelfPid
703− liftIO $ throwIO (ProcessExitException pid (createMessage reason))
728+ throwM (ProcessExitException pid (createMessage reason))
704
729
705-- | Forceful request to kill a process. Where 'exit' provides an exception
730-- | Forceful request to kill a process. Where 'exit' provides an exception
706-- 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
@@ -733,12 +758,12 @@ catchExit :: forall a b . (Show a, Serializable a)
733 => Process b
758 => Process b
734 -> (ProcessId -> a -> Process b)
759 -> (ProcessId -> a -> Process b)
735 -> Process b
760 -> Process b
736−catchExit act exitHandler = catch act handleExit
761+catchExit act exitHandler = Catch.catch act handleExit
737 where
762 where
738 handleExit ex@(ProcessExitException from msg) =
763 handleExit ex@(ProcessExitException from msg) =
739 if messageFingerprint msg == fingerprint (undefined :: a)
764 if messageFingerprint msg == fingerprint (undefined :: a)
740 then exitHandler from decoded
765 then exitHandler from decoded
741− else liftIO $ throwIO ex
766+ else throwM ex
742 where
767 where
743 decoded :: a
768 decoded :: a
744 -- 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
@@ -757,12 +782,12 @@ catchExit act exitHandler = catch act handleExit
757catchesExit :: Process b
782catchesExit :: Process b
758 -> [(ProcessId -> Message -> (Process (Maybe b)))]
783 -> [(ProcessId -> Message -> (Process (Maybe b)))]
759 -> Process b
784 -> Process b
760−catchesExit act handlers = catch act ((flip handleExit) handlers)
785+catchesExit act handlers = Catch.catch act ((flip handleExit) handlers)
761 where
786 where
762 handleExit :: ProcessExitException
787 handleExit :: ProcessExitException
763 -> [(ProcessId -> Message -> Process (Maybe b))]
788 -> [(ProcessId -> Message -> Process (Maybe b))]
764 -> Process b
789 -> Process b
765− handleExit ex [] = liftIO $ throwIO ex
790+ handleExit ex [] = throwM ex
766 handleExit ex@(ProcessExitException from msg) (h:hs) = do
791 handleExit ex@(ProcessExitException from msg) (h:hs) = do
767 r <- h from msg
792 r <- h from msg
768 case r of
793 case r of
@@ -871,12 +896,19 @@ monitor = monitor' . ProcessIdentifier
871-- @withMonitor@ returns, there might still be unreceived monitor
896-- @withMonitor@ returns, there might still be unreceived monitor
872-- messages in the queue.
897-- messages in the queue.
873--
898--
874−withMonitor :: ProcessId -> Process a -> Process a
899+withMonitor :: ProcessId -> (MonitorRef -> Process a) -> Process a
875−withMonitor pid code = bracket (monitor pid) unmonitor (\_ -> code)
900+withMonitor pid = bracket (monitor pid) unmonitor
876− -- unmonitor blocks waiting for the response, so there's a possibility
901+
877− -- that an exception might interrupt withMonitor before the unmonitor
902+-- | Establishes temporary monitoring of another process.
878− -- has completed. I think that's better than making the unmonitor
903+--
879− -- 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
880
912
881-- | Remove a link
913-- | Remove a link
882--
914--
@@ -914,12 +946,21 @@ unlinkPort sport = do
914-- | Remove a monitor
946-- | Remove a monitor
915--
947--
916-- 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.
917unmonitor :: MonitorRef -> Process ()
952unmonitor :: MonitorRef -> Process ()
918unmonitor ref = do
953unmonitor ref = do
919 unmonitorAsync ref
954 unmonitorAsync ref
920 receiveWait [ matchIf (\(DidUnmonitor ref') -> ref' == ref)
955 receiveWait [ matchIf (\(DidUnmonitor ref') -> ref' == ref)
921 (\_ -> return ())
956 (\_ -> return ())
922 ]
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+ ]
923
964
924--------------------------------------------------------------------------------
965--------------------------------------------------------------------------------
925-- Exception handling --
966-- Exception handling --
@@ -927,56 +968,43 @@ unmonitor ref = do
927
968
928-- | Lift 'Control.Exception.catch'
969-- | Lift 'Control.Exception.catch'
929catch :: Exception e => Process a -> (e -> Process a) -> Process a
970catch :: Exception e => Process a -> (e -> Process a) -> Process a
930−catch p h = do
971+catch = Catch.catch
931− lproc <- ask
972+{-# DEPRECATED catch "Use Control.Monad.Catch.catch instead" #-}
932− liftIO $ Ex.catch (runLocalProcess lproc p) (runLocalProcess lproc . h)
933
973
934-- | Lift 'Control.Exception.try'
974-- | Lift 'Control.Exception.try'
935try :: Exception e => Process a -> Process (Either e a)
975try :: Exception e => Process a -> Process (Either e a)
936−try p = do
976+try = Catch.try
937− lproc <- ask
977+{-# DEPRECATED try "Use Control.Monad.Catch.try instead" #-}
938− liftIO $ Ex.try (runLocalProcess lproc p)
939
978
940-- | Lift 'Control.Exception.mask'
979-- | Lift 'Control.Exception.mask'
941mask :: ((forall a. Process a -> Process a) -> Process b) -> Process b
980mask :: ((forall a. Process a -> Process a) -> Process b) -> Process b
942−mask p = do
981+mask = Catch.mask
943− lproc <- ask
982+{-# DEPRECATED mask "Use Control.Monad.Catch.mask_ instead" #-}
944− liftIO $ Ex.mask $ \restore ->
945− runLocalProcess lproc (p (liftRestore restore))
946− where
947− liftRestore :: (forall a. IO a -> IO a)
948− -> (forall a. Process a -> Process a)
949− liftRestore restoreIO = \p2 -> do
950− ourLocalProc <- ask
951− liftIO $ restoreIO $ runLocalProcess ourLocalProc p2
952
983
953-- | Lift 'Control.Exception.mask_'
984-- | Lift 'Control.Exception.mask_'
954mask_ :: Process a -> Process a
985mask_ :: Process a -> Process a
955−mask_ p = do
986+mask_ = Catch.mask_
956− lproc <- ask
987+{-# DEPRECATED mask_ "Use Control.Monad.Catch.mask_ instead" #-}
957− liftIO $ Ex.mask_ $ runLocalProcess lproc p
958
988
959-- | Lift 'Control.Exception.onException'
989-- | Lift 'Control.Exception.onException'
960onException :: Process a -> Process b -> Process a
990onException :: Process a -> Process b -> Process a
961−onException p what = p `catch` \e -> do _ <- what
991+onException = Catch.onException
962− liftIO $ throwIO (e :: SomeException)
992+{-# DEPRECATED onException "Use Control.Monad.Catch.onException instead" #-}
963
993
964-- | Lift 'Control.Exception.bracket'
994-- | Lift 'Control.Exception.bracket'
965bracket :: Process a -> (a -> Process b) -> (a -> Process c) -> Process c
995bracket :: Process a -> (a -> Process b) -> (a -> Process c) -> Process c
966−bracket before after thing =
996+bracket = Catch.bracket
967− mask $ \restore -> do
997+{-# DEPRECATED bracket "Use Control.Monad.Catch.bracket instead" #-}
968− a <- before
969− r <- restore (thing a) `onException` after a
970− _ <- after a
971− return r
972
998
973-- | Lift 'Control.Exception.bracket_'
999-- | Lift 'Control.Exception.bracket_'
974bracket_ :: Process a -> Process b -> Process c -> Process c
1000bracket_ :: Process a -> Process b -> Process c -> Process c
975−bracket_ before after thing = bracket before (const after) (const thing)
1001+bracket_ = Catch.bracket_
1002+{-# DEPRECATED bracket_ "Use Control.Monad.Catch.bracket_ instead" #-}
976
1003
977-- | Lift 'Control.Exception.finally'
1004-- | Lift 'Control.Exception.finally'
978finally :: Process a -> Process b -> Process a
1005finally :: Process a -> Process b -> Process a
979−finally a sequel = bracket_ (return ()) sequel a
1006+finally = Catch.finally
1007+{-# DEPRECATED finally "Use Control.Monad.Catch.finally instead" #-}
980
1008
981-- | You need this when using 'catches'
1009-- | You need this when using 'catches'
982data Handler a = forall e . Exception e => Handler (e -> Process a)
1010data Handler a = forall e . Exception e => Handler (e -> Process a)
@@ -986,10 +1014,10 @@ instance Functor Handler where
986
1014
987-- | Lift 'Control.Exception.catches'
1015-- | Lift 'Control.Exception.catches'
988catches :: Process a -> [Handler a] -> Process a
1016catches :: Process a -> [Handler a] -> Process a
989−catches proc handlers = proc `catch` catchesHandler handlers
1017+catches proc handlers = proc `Catch.catch` catchesHandler handlers
990
1018
991catchesHandler :: [Handler a] -> SomeException -> Process a
1019catchesHandler :: [Handler a] -> SomeException -> Process a
992−catchesHandler handlers e = foldr tryHandler (throw e) handlers
1020+catchesHandler handlers e = foldr tryHandler (throwM e) handlers
993 where tryHandler (Handler handler) res
1021 where tryHandler (Handler handler) res
994 = case fromException e of
1022 = case fromException e of
995 Just e' -> handler e'
1023 Just e' -> handler e'
@@ -1058,17 +1086,52 @@ unlinkPortAsync (SendPort cid) =
1058-- Logging --
1086-- Logging --
1059--------------------------------------------------------------------------------
1087--------------------------------------------------------------------------------
1060
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+
1061-- | Log a string
1124-- | Log a string
1062--
1125--
1063−-- @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
1064−-- to the process registered as 'logger'. By default, this process simply
1127+-- 'ProcessId' of the current process to the process registered as @logger@. By
1065−-- sends the string to 'stderr'. Individual Cloud Haskell backends might
1128+-- default, this process simply sends the string to @stderr@. Individual Cloud
1066−-- replace this with a different logger process, however.
1129+-- Haskell backends might replace this with a different logger process, however.
1067say :: String -> Process ()
1130say :: String -> Process ()
1068say string = do
1131say string = do
1069 now <- liftIO getCurrentTime
1132 now <- liftIO getCurrentTime
1070 us <- getSelfPid
1133 us <- getSelfPid
1071− nsend "logger" (formatTime defaultTimeLocale "%c" now, us, string)
1134+ nsend "logger" (SayMessage now us string)
1072
1135
1073--------------------------------------------------------------------------------
1136--------------------------------------------------------------------------------
1074-- Registry --
1137-- Registry --
@@ -1129,7 +1192,7 @@ unregister label = do
1129handleRegistrationReply :: String -> Bool -> Maybe ProcessId -> Process ()
1192handleRegistrationReply :: String -> Bool -> Maybe ProcessId -> Process ()
1130handleRegistrationReply label ok owner =
1193handleRegistrationReply label ok owner =
1131 when (not ok) $
1194 when (not ok) $
1132− liftIO $ throwIO $ ProcessRegistrationException label owner
1195+ throwM $ ProcessRegistrationException label owner
1133
1196
1134-- | Remove a process from a remote registry (asynchronous).
1197-- | Remove a process from a remote registry (asynchronous).
1135--
1198--
@@ -1164,8 +1227,12 @@ whereisRemoteAsync nid label = do
1164
1227
1165-- | Named send to a process in the local registry (asynchronous)
1228-- | Named send to a process in the local registry (asynchronous)
1166nsend :: Serializable a => String -> a -> Process ()
1229nsend :: Serializable a => String -> a -> Process ()
1167−nsend label msg =
1230+nsend label msg = do
1168− sendCtrlMsg Nothing (NamedSend label (createMessage msg))
1231+ proc <- ask
1232+ let msg' = createUnencodedMessage msg
1233+ liftIO $ traceEvent (localEventBus (processNode proc))
1234+ (MxSentToName label (processId proc) msg')
1235+ sendCtrlMsg Nothing (NamedSend label msg')
1169
1236
1170-- | Named send to a process in the local registry (asynchronous).
1237-- | Named send to a process in the local registry (asynchronous).
1171-- 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
@@ -1177,9 +1244,22 @@ unsafeNSend = Unsafe.nsend
1177-- | Named send to a process in a remote registry (asynchronous)
1244-- | Named send to a process in a remote registry (asynchronous)
1178nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
1245nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
1179nsendRemote nid label msg = do
1246nsendRemote nid label msg = do
1180− here <- getSelfNode
1247+ proc <- ask
1181− if here == nid then nsend label msg
1248+ let us = processId proc
1182− else sendCtrlMsg (Just nid) (NamedSend label (createMessage msg))
1249+ let node = processNode proc
1250+ if localNodeId node == nid
1251+ then nsend label msg
1252+ else let lbl = label ++ "@" ++ show nid in do
1253+ liftIO $ traceEvent (localEventBus node)
1254+ (MxSentToName lbl us (wrapMessage msg))
1255+ sendCtrlMsg (Just nid) (NamedSend label (createMessage msg))
1256+
1257+-- | Named send to a process in a remote registry (asynchronous)
1258+-- This function makes /no/ attempt to serialize and (in the case when the
1259+-- destination process resides on the same local node) therefore ensure that
1260+-- the payload is fully evaluated before it is delivered.
1261+unsafeNSendRemote :: Serializable a => NodeId -> String -> a -> Process ()
1262+unsafeNSendRemote = Unsafe.nsendRemote
1183
1263
1184--------------------------------------------------------------------------------
1264--------------------------------------------------------------------------------
1185-- Closures --
1265-- Closures --
src/Control/Distributed/Process/Internal/Spawn.hs+3−3
@@ -43,7 +43,7 @@ import Control.Distributed.Process.Internal.Closure.BuiltIn
43 )
43 )
44import Control.Distributed.Process.Internal.Primitives
44import Control.Distributed.Process.Internal.Primitives
45 ( -- Basic messaging
45 ( -- Basic messaging
46− send
46+ usend
47 , expect
47 , expect
48 , receiveWait
48 , receiveWait
49 , match
49 , match
@@ -71,7 +71,7 @@ spawn nid proc = do
71 receiveWait [
71 receiveWait [
72 matchIf (\(DidSpawn ref _) -> ref == sRef) $ \(DidSpawn _ pid) -> do
72 matchIf (\(DidSpawn ref _) -> ref == sRef) $ \(DidSpawn _ pid) -> do
73 unmonitor mRef
73 unmonitor mRef
74− send pid ()
74+ usend pid ()
75 return pid
75 return pid
76 , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef) $ \_ ->
76 , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef) $ \_ ->
77 return (nullProcessId nid)
77 return (nullProcessId nid)
@@ -118,7 +118,7 @@ call dict nid proc = do
118 cpDelayed us (returnCP sdictUnit ())
118 cpDelayed us (returnCP sdictUnit ())
119 )
119 )
120 mResult <- receiveWait
120 mResult <- receiveWait
121− [ match $ \a -> send pid () >> return (Right a)
121+ [ match $ \a -> usend pid () >> return (Right a)
122 , matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)
122 , matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)
123 (\(ProcessMonitorNotification _ _ reason) -> return (Left reason))
123 (\(ProcessMonitorNotification _ _ reason) -> return (Left reason))
124 ]
124 ]
src/Control/Distributed/Process/Internal/StrictMVar.hs+8−4
@@ -36,8 +36,8 @@ import qualified Control.Concurrent.MVar as MVar
36#endif
36#endif
37 )
37 )
38import GHC.MVar (MVar(MVar))
38import GHC.MVar (MVar(MVar))
39−import GHC.IO (IO(IO))
39+import GHC.IO (IO(IO), unIO)
40−import GHC.Prim (mkWeak#)
40+import GHC.Exts (mkWeak#)
41import GHC.Weak (Weak(Weak))
41import GHC.Weak (Weak(Weak))
42
42
43newtype StrictMVar a = StrictMVar (MVar.MVar a)
43newtype StrictMVar a = StrictMVar (MVar.MVar a)
@@ -85,5 +85,9 @@ modifyMVarMasked (StrictMVar v) f =
85 evaluateFst (x, y) = evaluate x >> return (x, y)
85 evaluateFst (x, y) = evaluate x >> return (x, y)
86
86
87mkWeakMVar :: StrictMVar a -> IO () -> IO (Weak (StrictMVar a))
87mkWeakMVar :: StrictMVar a -> IO () -> IO (Weak (StrictMVar a))
88−mkWeakMVar m@(StrictMVar (MVar m#)) f = IO $ \s ->
88+mkWeakMVar q@(StrictMVar (MVar m#)) f = IO $ \s ->
89− case mkWeak# m# m f s of (# s1, w #) -> (# s1, Weak w #)
89+#if MIN_VERSION_base(4,9,0)
90+ case mkWeak# m# q (unIO f) s of (# s', w #) -> (# s', Weak w #)
91+#else
92+ case mkWeak# m# q f s of (# s', w #) -> (# s', Weak w #)
93+#endif
src/Control/Distributed/Process/Internal/Types.hs+52−4
@@ -4,6 +4,9 @@
4{-# LANGUAGE GADTs #-}
4{-# LANGUAGE GADTs #-}
5{-# LANGUAGE DeriveGeneric #-}
5{-# LANGUAGE DeriveGeneric #-}
6{-# LANGUAGE CPP #-}
6{-# LANGUAGE CPP #-}
7+{-# LANGUAGE MultiParamTypeClasses #-}
8+{-# LANGUAGE TypeFamilies #-}
9+{-# LANGUAGE Rank2Types #-}
7
10
8-- | Types used throughout the Cloud Haskell framework
11-- | Types used throughout the Cloud Haskell framework
9--
12--
@@ -112,11 +115,12 @@ import Control.Concurrent (ThreadId)
112import Control.Concurrent.Chan (Chan)
115import Control.Concurrent.Chan (Chan)
113import Control.Concurrent.STM (STM)
116import Control.Concurrent.STM (STM)
114import Control.Concurrent.STM.TChan (TChan)
117import Control.Concurrent.STM.TChan (TChan)
118+import Control.Monad.Catch (MonadThrow(..), MonadCatch(..), MonadMask(..))
115import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)
119import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)
116−import Control.Applicative (Applicative, Alternative, (<$>), (<*>))
120+import Control.Applicative
117import Control.Monad.Fix (MonadFix)
121import Control.Monad.Fix (MonadFix)
118import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
122import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
119−import Control.Monad.IO.Class (MonadIO)
123+import Control.Monad.IO.Class (MonadIO(..))
120import Control.Distributed.Process.Serializable
124import Control.Distributed.Process.Serializable
121 ( Fingerprint
125 ( Fingerprint
122 , Serializable
126 , Serializable
@@ -139,6 +143,7 @@ import qualified Control.Distributed.Process.Internal.StrictContainerAccessors a
139
143
140import Data.Hashable
144import Data.Hashable
141import GHC.Generics
145import GHC.Generics
146+import Prelude
142
147
143--------------------------------------------------------------------------------
148--------------------------------------------------------------------------------
144-- Node and process identifiers --
149-- Node and process identifiers --
@@ -345,7 +350,50 @@ data LocalProcessState = LocalProcessState
345newtype Process a = Process {
350newtype Process a = Process {
346 unProcess :: ReaderT LocalProcess IO a
351 unProcess :: ReaderT LocalProcess IO a
347 }
352 }
348− deriving (Functor, Monad, MonadIO, MonadReader LocalProcess, Typeable, Applicative, MonadFix)
353+ deriving ( Applicative
354+ , Functor
355+ , Monad
356+ , MonadFix
357+ , MonadIO
358+ , MonadReader LocalProcess
359+ , Typeable
360+ )
361+
362+instance MonadThrow Process where
363+ throwM = liftIO . throwIO
364+instance MonadCatch Process where
365+ catch p h = do
366+ lproc <- ask
367+ liftIO $ catch (runLocalProcess lproc p) (runLocalProcess lproc . h)
368+instance MonadMask Process where
369+#if MIN_VERSION_exceptions(0,10,0)
370+ generalBracket acquire release inner = do
371+ lproc <- ask
372+ liftIO $
373+ generalBracket (runLocalProcess lproc acquire)
374+ (\a e -> runLocalProcess lproc $ release a e)
375+ (runLocalProcess lproc . inner)
376+#endif
377+ mask p = do
378+ lproc <- ask
379+ liftIO $ mask $ \restore ->
380+ runLocalProcess lproc (p (liftRestore restore))
381+ where
382+ liftRestore :: (forall a. IO a -> IO a)
383+ -> (forall a. Process a -> Process a)
384+ liftRestore restoreIO = \p2 -> do
385+ ourLocalProc <- ask
386+ liftIO $ restoreIO $ runLocalProcess ourLocalProc p2
387+ uninterruptibleMask p = do
388+ lproc <- ask
389+ liftIO $ uninterruptibleMask $ \restore ->
390+ runLocalProcess lproc (p (liftRestore restore))
391+ where
392+ liftRestore :: (forall a. IO a -> IO a)
393+ -> (forall a. Process a -> Process a)
394+ liftRestore restoreIO = \p2 -> do
395+ ourLocalProc <- ask
396+ liftIO $ restoreIO $ runLocalProcess ourLocalProc p2
349
397
350--------------------------------------------------------------------------------
398--------------------------------------------------------------------------------
351-- Typed channels --
399-- Typed channels --
@@ -589,7 +637,7 @@ newtype DidUnlinkPort = DidUnlinkPort SendPortId
589
637
590-- | 'SpawnRef' are used to return pids of spawned processes
638-- | 'SpawnRef' are used to return pids of spawned processes
591newtype SpawnRef = SpawnRef Int32
639newtype SpawnRef = SpawnRef Int32
592− deriving (Show, Binary, Typeable, Eq)
640+ deriving (Show, Binary, Typeable, Eq, Ord)
593
641
594-- | (Asynchronius) reply from 'spawn'
642-- | (Asynchronius) reply from 'spawn'
595data DidSpawn = DidSpawn SpawnRef ProcessId
643data DidSpawn = DidSpawn SpawnRef ProcessId
src/Control/Distributed/Process/Internal/WeakTQueue.hs+7−2
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2{-# LANGUAGE DeriveDataTypeable #-}
2{-# LANGUAGE MagicHash, UnboxedTuples #-}
3{-# LANGUAGE MagicHash, UnboxedTuples #-}
3-- | Clone of Control.Concurrent.STM.TQueue with support for mkWeakTQueue
4-- | Clone of Control.Concurrent.STM.TQueue with support for mkWeakTQueue
@@ -22,8 +23,8 @@ module Control.Distributed.Process.Internal.WeakTQueue (
22import Prelude hiding (read)
23import Prelude hiding (read)
23import GHC.Conc
24import GHC.Conc
24import Data.Typeable (Typeable)
25import Data.Typeable (Typeable)
25−import GHC.IO (IO(IO))
26+import GHC.IO (IO(IO), unIO)
26−import GHC.Prim (mkWeak#)
27+import GHC.Exts (mkWeak#)
27import GHC.Weak (Weak(Weak))
28import GHC.Weak (Weak(Weak))
28
29
29--------------------------------------------------------------------------------
30--------------------------------------------------------------------------------
@@ -99,4 +100,8 @@ isEmptyTQueue (TQueue read write) = do
99
100
100mkWeakTQueue :: TQueue a -> IO () -> IO (Weak (TQueue a))
101mkWeakTQueue :: TQueue a -> IO () -> IO (Weak (TQueue a))
101mkWeakTQueue 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
102 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+41−83
@@ -76,6 +76,40 @@
76--
76--
77-- * Whether messages will be taken from the mailbox first, or the event bus.
77-- * Whether messages will be taken from the mailbox first, or the event bus.
78--
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.
112+--
79-- [Management Data API]
113-- [Management Data API]
80--
114--
81-- Both management agents and clients of the API have access to a variety of
115-- Both management agents and clients of the API have access to a variety of
@@ -83,28 +117,8 @@
83-- system information. Agents maintain their own internal state privately (via a
117-- system information. Agents maintain their own internal state privately (via a
84-- state transformer - see 'mxGetLocal' et al), however it is possible for
118-- state transformer - see 'mxGetLocal' et al), however it is possible for
85-- agents to share additional data with each other (and the outside world)
119-- agents to share additional data with each other (and the outside world)
86−-- using /data tables/.
120+-- using whatever mechanism the user wishes, e.g., acidstate, or shared memory
87−--
121+-- primitives.
88−-- Each agent is assigned its own data table, which acts as a shared map, where
89−-- the keys are @String@s and the values are @Serializable@ datum of whatever
90−-- type the agent or its clients stores.
91−--
92−-- Because an agent's /data table/ stores its values in raw 'Message' format,
93−-- it works effectively as an /un-typed dictionary/, into which data of varying
94−-- types can be fed and later retrieved. The upside of this is that different
95−-- keys can be mapped to various types without any additional work on the part
96−-- of the developer. The downside is that the code reading these values must
97−-- know in advance what type(s) to expect, and the API provides no additional
98−-- support for handling that.
99−--
100−-- Publishing is accomplished using the 'mxPublish' and 'mxSet' APIs, whilst
101−-- querying and deletion are handled by 'mxGet', 'mxClear', 'mxPurgeTable' and
102−-- 'mxDropTable' respectively.
103−--
104−-- When a management agent terminates, their tables are left in memory despite
105−-- termination, such that an agent may resume its role (by restarting) or have
106−-- its 'MxAgentId' taken over by another subsequent agent, leaving the data
107−-- originally captured in place.
108--
122--
109-- [Defining Agents]
123-- [Defining Agents]
110--
124--
@@ -164,8 +178,6 @@
164-- > monitorNames = getSelfPid >>= nsend "name-monitor"
178-- > monitorNames = getSelfPid >>= nsend "name-monitor"
165-- > monitorNames2 = getSelfPid >>= mxNotify
179-- > monitorNames2 = getSelfPid >>= mxNotify
166--
180--
167−-- For some real-world examples, see the distributed-process-platform package.
168−--
169-- [Performance, Stablity and Scalability]
181-- [Performance, Stablity and Scalability]
170--
182--
171-- /Management Agents/ offer numerous advantages over regular processes:
183-- /Management Agents/ offer numerous advantages over regular processes:
@@ -194,10 +206,6 @@
194-- the event bus /and/ their own mailboxes, plus searching through the set of
206-- the event bus /and/ their own mailboxes, plus searching through the set of
195-- event sinks (for each agent) to determine the right handler for the event.
207-- event sinks (for each agent) to determine the right handler for the event.
196--
208--
197−-- Each management agent requires not only its own @Process@ (in which the agent
198−-- code is run), but also a peer process that provides its /data table/. These
199−-- data tables also have to be coordinated and manaaged on each agent's behalf.
200−--
201-- [Architecture Overview]
209-- [Architecture Overview]
202--
210--
203-- The architecture of the management event bus is internal and subject to
211-- The architecture of the management event bus is internal and subject to
@@ -264,16 +272,9 @@ module Control.Distributed.Process.Management
264 , mxGetLocal
272 , mxGetLocal
265 , mxUpdateLocal
273 , mxUpdateLocal
266 , liftMX
274 , liftMX
267− -- * Mx Data API
268− , mxPublish
269− , mxSet
270− , mxGet
271− , mxClear
272− , mxPurgeTable
273− , mxDropTable
274 ) where
275 ) where
275
276
276−import Control.Applicative ((<$>))
277+import Control.Applicative
277import Control.Concurrent.STM (atomically)
278import Control.Concurrent.STM (atomically)
278import Control.Concurrent.STM.TChan
279import Control.Concurrent.STM.TChan
279 ( readTChan
280 ( readTChan
@@ -281,14 +282,10 @@ import Control.Concurrent.STM.TChan
281 , TChan
282 , TChan
282 )
283 )
283import Control.Distributed.Process.Internal.Primitives
284import Control.Distributed.Process.Internal.Primitives
284− ( newChan
285+ ( receiveWait
285− , nsend
286− , receiveWait
287− , matchChan
288 , matchAny
286 , matchAny
289 , matchSTM
287 , matchSTM
290 , unwrapMessage
288 , unwrapMessage
291− , onException
292 , register
289 , register
293 , whereis
290 , whereis
294 , die
291 , die
@@ -303,26 +300,26 @@ import Control.Distributed.Process.Internal.Types
303 , unsafeCreateUnencodedMessage
300 , unsafeCreateUnencodedMessage
304 )
301 )
305import Control.Distributed.Process.Management.Internal.Bus (publishEvent)
302import Control.Distributed.Process.Management.Internal.Bus (publishEvent)
306−import qualified Control.Distributed.Process.Management.Internal.Table as Table
307import Control.Distributed.Process.Management.Internal.Types
303import Control.Distributed.Process.Management.Internal.Types
308 ( MxAgentId(..)
304 ( MxAgentId(..)
309 , MxAgent(..)
305 , MxAgent(..)
310 , MxAction(..)
306 , MxAction(..)
311 , ChannelSelector(..)
307 , ChannelSelector(..)
312 , MxAgentState(..)
308 , MxAgentState(..)
313− , MxAgentStart(..)
314 , MxSink
309 , MxSink
315 , MxEvent(..)
310 , MxEvent(..)
316 )
311 )
317import Control.Distributed.Process.Serializable (Serializable)
312import Control.Distributed.Process.Serializable (Serializable)
318import Control.Monad.IO.Class (liftIO)
313import Control.Monad.IO.Class (liftIO)
319import Control.Monad.Reader (ask)
314import Control.Monad.Reader (ask)
315+import Control.Monad.Catch (onException)
320import qualified Control.Monad.State as ST
316import qualified Control.Monad.State as ST
321 ( get
317 ( get
322 , modify
318 , modify
323 , lift
319 , lift
324 , runStateT
320 , runStateT
325 )
321 )
322+import Prelude
326
323
327-- | Publishes an arbitrary @Serializable@ message to the management event bus.
324-- | Publishes an arbitrary @Serializable@ message to the management event bus.
328-- 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
@@ -334,42 +331,6 @@ mxNotify msg = do
334 bus <- localEventBus . processNode <$> ask
331 bus <- localEventBus . processNode <$> ask
335 liftIO $ publishEvent bus $ unsafeCreateUnencodedMessage msg
332 liftIO $ publishEvent bus $ unsafeCreateUnencodedMessage msg
336
333
337−-- | Publish an arbitrary @Message@ as a property in the management database.
338−--
339−-- For publishing @Serializable@ data, use 'mxSet' instead.
340−--
341−mxPublish :: MxAgentId -> String -> Message -> Process ()
342−mxPublish a k v = Table.set k v (Table.MxForAgent a)
343−
344−-- | Sets an arbitrary @Serializable@ datum against a key in the management
345−-- database. Note that /no attempt is made to force the argument/, therefore
346−-- it is very important that you do not pass unevaluated thunks that might
347−-- crash some other, arbitrary process (or management agent!) that obtains
348−-- and attempts to force the value later on.
349−--
350−mxSet :: Serializable a => MxAgentId -> String -> a -> Process ()
351−mxSet mxId key msg = do
352− Table.set key (unsafeCreateUnencodedMessage msg) (Table.MxForAgent mxId)
353−
354−-- | Fetches a property from the management database for the given key.
355−-- If the property is not set, or does not match the expected type when
356−-- typechecked (at runtime), returns @Nothing@.
357−mxGet :: Serializable a => MxAgentId -> String -> Process (Maybe a)
358−mxGet = Table.fetch . Table.MxForAgent
359−
360−-- | Clears a property from the management database using the given key.
361−-- If the key does not exist in the database, this is a noop.
362−mxClear :: MxAgentId -> String -> Process ()
363−mxClear mxId key = Table.clear key (Table.MxForAgent mxId)
364−
365−-- | Purges a table in the management database of all its stored properties.
366−mxPurgeTable :: MxAgentId -> Process ()
367−mxPurgeTable = Table.purge . Table.MxForAgent
368−
369−-- | Deletes a table from the management database.
370−mxDropTable :: MxAgentId -> Process ()
371−mxDropTable = Table.delete . Table.MxForAgent
372−
373--------------------------------------------------------------------------------
334--------------------------------------------------------------------------------
374-- API for writing user defined management extensions (i.e., agents) --
335-- API for writing user defined management extensions (i.e., agents) --
375--------------------------------------------------------------------------------
336--------------------------------------------------------------------------------
@@ -489,10 +450,7 @@ mxAgentWithFinalize mxId initState handlers dtor = do
489 return pid
450 return pid
490 where
451 where
491 start (sendTChan, recvTChan) = do
452 start (sendTChan, recvTChan) = do
492− (sp, rp) <- newChan
453+ let nState = MxAgentState mxId sendTChan initState
493− nsend Table.mxTableCoordinator (MxAgentStart sp mxId)
494− tablePid <- receiveWait [ matchChan rp (\(p :: ProcessId) -> return p) ]
495− let nState = MxAgentState mxId sendTChan tablePid initState
496 runAgent dtor handlers InputChan recvTChan nState
454 runAgent dtor handlers InputChan recvTChan nState
497
455
498 runAgent :: MxAgent s ()
456 runAgent :: MxAgent s ()
src/Control/Distributed/Process/Management/Internal/Agent.hs+2−1
@@ -3,7 +3,7 @@
3
3
4module Control.Distributed.Process.Management.Internal.Agent where
4module Control.Distributed.Process.Management.Internal.Agent where
5
5
6−import Control.Applicative ((<$>))
6+import Control.Applicative
7import Control.Concurrent (forkIO)
7import Control.Concurrent (forkIO)
8import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
8import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
9import Control.Concurrent.STM (atomically)
9import Control.Concurrent.STM (atomically)
@@ -44,6 +44,7 @@ import Control.Monad (void)
44import Control.Monad.IO.Class (liftIO)
44import Control.Monad.IO.Class (liftIO)
45import Control.Monad.Reader (ask)
45import Control.Monad.Reader (ask)
46import GHC.Weak (Weak, deRefWeak)
46import GHC.Weak (Weak, deRefWeak)
47+import Prelude
47
48
48--------------------------------------------------------------------------------
49--------------------------------------------------------------------------------
49-- Agent Controller Implementation --
50-- Agent Controller Implementation --
src/Control/Distributed/Process/Management/Internal/Table.hsdeleted−222
@@ -1,222 +0,0 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
2−{-# LANGUAGE ScopedTypeVariables #-}
3−{-# LANGUAGE RankNTypes #-}
4−{-# LANGUAGE DeriveGeneric #-}
5−{-# LANGUAGE RecordWildCards #-}
6−
7−module Control.Distributed.Process.Management.Internal.Table
8− ( MxTableRequest(..)
9− , MxTableId(..)
10− , mxTableCoordinator
11− , startTableCoordinator
12− , delete
13− , purge
14− , clear
15− , set
16− , get
17− , fetch
18− ) where
19−
20−import Control.Distributed.Process.Internal.Primitives
21− ( receiveWait
22− , receiveChan
23− , match
24− , matchAny
25− , matchIf
26− , matchChan
27− , send
28− , nsend
29− , sendChan
30− , getSelfPid
31− , link
32− , monitor
33− , unwrapMessage
34− , newChan
35− , withMonitor
36− )
37−import Control.Distributed.Process.Internal.Types
38− ( Process
39− , ProcessId
40− , ProcessMonitorNotification(..)
41− , SendPort
42− , ReceivePort
43− , Message
44− , unsafeCreateUnencodedMessage
45− )
46−import Control.Distributed.Process.Management.Internal.Types
47− ( MxTableId(..)
48− , MxAgentId(..)
49− , MxAgentStart(..)
50− , Fork)
51−import Control.Distributed.Process.Serializable (Serializable)
52−import Control.Monad.IO.Class (liftIO)
53−import Data.Accessor (Accessor, accessor, (^=), (^:))
54−import Data.Binary (Binary)
55−import Data.Map (Map)
56−import qualified Data.Map as Map
57−import Data.Typeable (Typeable)
58−
59−import GHC.Generics
60−
61−-- An extremely lightweight shared Map implementation, for use
62−-- by /management agents/ and their cohorts. Each agent is assigned
63−-- a table, into which any serializable @Message@ can be inserted.
64−-- Data are inserted, removed and searched for via their key, which
65−-- is a string. Tables can be purged, values can be set, fetched or
66−-- cleared/removed.
67−--
68−
69−data MxTableRequest =
70− Delete
71− | Purge
72− | Clear !String
73− | Set !String !Message
74− | Get !String !(SendPort (Maybe Message)) -- see [note: un-typed send port]
75− deriving (Typeable, Generic)
76−instance Binary MxTableRequest where
77−
78−data MxTableState = MxTableState { _name :: !String
79− , _entries :: !(Map String Message)
80− }
81−
82−type MxTables = Map MxAgentId ProcessId
83−
84−mxTableCoordinator :: String
85−mxTableCoordinator = "mx.table.coordinator"
86−
87−delete :: MxTableId -> Process ()
88−delete = sendReq Delete
89−
90−purge :: MxTableId -> Process ()
91−purge = sendReq Purge
92−
93−clear :: String -> MxTableId -> Process ()
94−clear k = sendReq (Clear k)
95−
96−set :: String -> Message -> MxTableId -> Process ()
97−set k v = sendReq (Set k v)
98−
99−fetch :: forall a. (Serializable a)
100− => MxTableId
101− -> String
102− -> Process (Maybe a)
103−fetch (MxForPid pid) key = get pid key
104−fetch mxId@(MxForAgent _) key = do
105− (sp, rp) <- newChan :: Process (SendPort (Maybe Message),
106− ReceivePort (Maybe Message))
107− sendReq (Get key sp) mxId
108− receiveChan rp >>= maybe (return Nothing)
109− (unwrapMessage :: Message -> Process (Maybe a))
110−
111−-- [note: un-typed send port]
112−-- Here, fetch uses a typed channel over a raw Message to obtain
113−-- its result, so type checking is deferred until receipt and will
114−-- be handled in the caller's thread. This is necessary because
115−-- the server portion of the code knows nothing about the types
116−-- involved, nor should it, since these tables can be used to
117−-- store arbitrary serializable data.
118−
119−get :: forall a. (Serializable a)
120− => ProcessId
121− -> String
122− -> Process (Maybe a)
123−get pid key = do
124− safeFetch pid key >>= maybe (return Nothing)
125− (unwrapMessage :: Message -> Process (Maybe a))
126−
127−safeFetch :: ProcessId -> String -> Process (Maybe Message)
128−safeFetch pid key = do
129− (sp, rp) <- newChan
130− send pid $ Get key sp
131− withMonitor pid $ do
132− receiveWait [
133− matchChan rp return
134− , matchIf (\(ProcessMonitorNotification _ pid' _) -> pid' == pid)
135− (\_ -> return $ Just (unsafeCreateUnencodedMessage ()))
136− ]
137−
138−sendReq :: MxTableRequest -> MxTableId -> Process ()
139−sendReq req tid = (resolve tid) req
140−
141−resolve :: Serializable a => MxTableId -> (a -> Process ())
142−resolve (MxForAgent agent) = \msg -> nsend mxTableCoordinator (agent, msg)
143−resolve (MxForPid pid) = \msg -> send pid msg
144−
145−startTableCoordinator :: Fork -> Process ()
146−startTableCoordinator fork = run Map.empty
147− where
148− run :: MxTables -> Process ()
149− run tables =
150− receiveWait [
151− -- note that this state change can race with MxAgentStart requests
152− match (\(ProcessMonitorNotification _ pid _) -> do
153− return $ Map.filter (/= pid) tables)
154− , match (\(MxAgentStart ch agent) -> do
155− lookupAgent tables agent >>= \(p, t) -> do
156− sendChan ch p >> return t)
157− , match (\req@(agent, tReq :: MxTableRequest) -> do
158− case tReq of
159− Get k sp -> do
160− lookupAgent tables agent >>= \(p, t) -> do
161− safeFetch p k >>= sendChan sp >> return t
162− _ -> do
163− handleRequest tables req)
164− , matchAny (\_ -> return tables) -- unrecognised messages are dropped
165− ] >>= run
166−
167− handleRequest :: MxTables
168− -> (MxAgentId, MxTableRequest)
169− -> Process MxTables
170− handleRequest tables' (agent, req) = do
171− lookupAgent tables' agent >>= \(p, t) -> send p req >> return t
172−
173− lookupAgent :: MxTables -> MxAgentId -> Process (ProcessId, MxTables)
174− lookupAgent tables' agentId' = do
175− case Map.lookup agentId' tables' of
176− Nothing -> launchNew agentId' tables'
177− Just p -> return (p, tables')
178−
179− launchNew :: MxAgentId
180− -> MxTables
181− -> Process (ProcessId, MxTables)
182− launchNew mxId tblMap = do
183− let initState = MxTableState { _name = (agentId mxId)
184− , _entries = Map.empty
185− }
186− (pid, _) <- spawnSup $ tableHandler initState
187− return $ (pid, mxId `seq` pid `seq` Map.insert mxId pid tblMap)
188−
189− spawnSup proc = do
190− us <- getSelfPid
191− -- we need to use that passed in "fork", in order to
192− -- break an import cycle with Node.hs courtesy of the
193− -- management agent, API and tracing modules
194− them <- liftIO $ fork $ link us >> proc
195− ref <- monitor them
196− return (them, ref)
197−
198−tableHandler :: MxTableState -> Process ()
199−tableHandler state = do
200− ns <- receiveWait [
201− match (handleTableRequest state)
202− , matchAny (\_ -> return (Just state))
203− ]
204− case ns of
205− Nothing -> return ()
206− Just s' -> tableHandler s'
207− where
208− handleTableRequest _ Delete = return Nothing
209− handleTableRequest st Purge = return $ Just $ (entries ^= Map.empty) $ st
210− handleTableRequest st (Clear k) = return $ Just $ (entries ^: (k `seq` Map.delete k)) $ st
211− handleTableRequest st (Set k v) = return $ Just $ (entries ^: (k `seq` v `seq` Map.insert k v)) st
212− handleTableRequest st (Get k c) = getEntry k c st >> return (Just st)
213−
214−getEntry :: String
215− -> SendPort (Maybe Message)
216− -> MxTableState
217− -> Process ()
218−getEntry k m MxTableState{..} = do
219− sendChan m =<< return (Map.lookup k _entries)
220−
221−entries :: Accessor MxTableState (Map String Message)
222−entries = accessor _entries (\ls st -> st { _entries = ls })
src/Control/Distributed/Process/Management/Internal/Trace/Primitives.hs+10−3
@@ -28,11 +28,12 @@ module Control.Distributed.Process.Management.Internal.Trace.Primitives
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 )
37import Control.Distributed.Process.Management.Internal.Trace.Types
38import Control.Distributed.Process.Management.Internal.Trace.Types
38 ( TraceArg(..)
39 ( TraceArg(..)
@@ -67,6 +68,7 @@ import Control.Monad.IO.Class (liftIO)
67import Control.Monad.Reader (ask)
68import Control.Monad.Reader (ask)
68
69
69import qualified Data.Set as Set (fromList)
70import qualified Data.Set as Set (fromList)
71+import Prelude
70
72
71--------------------------------------------------------------------------------
73--------------------------------------------------------------------------------
72-- Main API --
74-- Main API --
@@ -167,6 +169,11 @@ withRegisteredTracer act = do
167 withLocalTracer $ \t -> liftIO $ Tracer.getCurrentTraceClient t sp
169 withLocalTracer $ \t -> liftIO $ Tracer.getCurrentTraceClient t sp
168 currentTracer <- receiveChan rp
170 currentTracer <- receiveChan rp
169 case currentTracer of
171 case currentTracer of
170− Nothing -> do { (Just p') <- whereis "tracer.initial"; act p' }
172+ Nothing -> do mTP <- whereis "tracer.initial"
173+ -- NB: this should NOT ever happen, but forcing pattern matches
174+ -- is not considered cool in later versions of MonadFail
175+ case mTP of
176+ Just p' -> act p'
177+ Nothing -> die $ "System Invariant Violation: Tracer Process "
178+ ++ "Name Not Found (whereis tracer.initial)"
171 (Just p) -> act p
179 (Just p) -> act p
172−
src/Control/Distributed/Process/Management/Internal/Trace/Tracer.hs+7−4
@@ -11,7 +11,7 @@ module Control.Distributed.Process.Management.Internal.Trace.Tracer
11 , eventLogTracer
11 , eventLogTracer
12 ) where
12 ) where
13
13
14−import Control.Applicative ((<$>))
14+import Control.Applicative
15import Control.Concurrent.Chan (writeChan)
15import Control.Concurrent.Chan (writeChan)
16import Control.Concurrent.MVar
16import Control.Concurrent.MVar
17 ( MVar
17 ( MVar
@@ -21,9 +21,7 @@ import Control.Distributed.Process.Internal.CQueue
21 ( CQueue
21 ( CQueue
22 )
22 )
23import Control.Distributed.Process.Internal.Primitives
23import Control.Distributed.Process.Internal.Primitives
24− ( catch
24+ ( die
25− , finally
26− , die
27 , receiveWait
25 , receiveWait
28 , forward
26 , forward
29 , sendChan
27 , sendChan
@@ -63,6 +61,10 @@ import Control.Distributed.Process.Internal.Types
63
61
64import Control.Monad.IO.Class (liftIO)
62import Control.Monad.IO.Class (liftIO)
65import Control.Monad.Reader (ask)
63import Control.Monad.Reader (ask)
64+import Control.Monad.Catch
65+ ( catch
66+ , finally
67+ )
66
68
67import Data.Set (Set)
69import Data.Set (Set)
68import qualified Data.Set as Set
70import qualified Data.Set as Set
@@ -73,6 +75,7 @@ import Data.Maybe (fromMaybe)
73import Data.Time.Clock (getCurrentTime)
75import Data.Time.Clock (getCurrentTime)
74import Data.Time.Format (formatTime)
76import Data.Time.Format (formatTime)
75import Debug.Trace (traceEventIO)
77import Debug.Trace (traceEventIO)
78+import Prelude
76
79
77#if ! MIN_VERSION_base(4,6,0)
80#if ! MIN_VERSION_base(4,6,0)
78import Prelude hiding (catch)
81import Prelude hiding (catch)
src/Control/Distributed/Process/Management/Internal/Trace/Types.hs−10
@@ -43,7 +43,6 @@ import Control.Distributed.Process.Serializable
43import Data.Binary
43import Data.Binary
44import Data.List (intersperse)
44import Data.List (intersperse)
45import Data.Set (Set)
45import Data.Set (Set)
46−import qualified Data.Set as Set (fromList)
47import Data.Typeable
46import Data.Typeable
48import GHC.Generics
47import GHC.Generics
49
48
@@ -158,12 +157,3 @@ getTraceFlags t s = publishEvent t (unsafeCreateUnencodedMessage s)
158
157
159getCurrentTraceClient :: MxEventBus -> SendPort (Maybe ProcessId) -> IO ()
158getCurrentTraceClient :: MxEventBus -> SendPort (Maybe ProcessId) -> IO ()
160getCurrentTraceClient t s = publishEvent t (unsafeCreateUnencodedMessage s)
159getCurrentTraceClient t s = publishEvent t (unsafeCreateUnencodedMessage s)
161−
162−class Traceable a where
163− uod :: [a] -> TraceSubject
164−
165−instance Traceable ProcessId where
166− uod = TraceProcs . Set.fromList
167−
168−instance Traceable String where
169− uod = TraceNames . Set.fromList
src/Control/Distributed/Process/Management/Internal/Types.hs+9−19
@@ -1,16 +1,13 @@
1{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2−{-# LANGUAGE StandaloneDeriving #-}
3{-# LANGUAGE ExistentialQuantification #-}
2{-# LANGUAGE ExistentialQuantification #-}
4{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5{-# LANGUAGE DeriveGeneric #-}
4{-# LANGUAGE DeriveGeneric #-}
6module Control.Distributed.Process.Management.Internal.Types
5module Control.Distributed.Process.Management.Internal.Types
7 ( MxAgentId(..)
6 ( MxAgentId(..)
8− , MxTableId(..)
9 , MxAgentState(..)
7 , MxAgentState(..)
10 , MxAgent(..)
8 , MxAgent(..)
11 , MxAction(..)
9 , MxAction(..)
12 , ChannelSelector(..)
10 , ChannelSelector(..)
13− , MxAgentStart(..)
14 , Fork
11 , Fork
15 , MxSink
12 , MxSink
16 , MxEvent(..)
13 , MxEvent(..)
@@ -24,8 +21,8 @@ import Control.Concurrent.STM
24import Control.Distributed.Process.Internal.Types
21import Control.Distributed.Process.Internal.Types
25 ( Process
22 ( Process
26 , ProcessId
23 , ProcessId
24+ , SendPortId
27 , Message
25 , Message
28− , SendPort
29 , DiedReason
26 , DiedReason
30 , NodeId
27 , NodeId
31 )
28 )
@@ -34,6 +31,7 @@ import qualified Control.Monad.State as ST
34 ( MonadState
31 ( MonadState
35 , StateT
32 , StateT
36 )
33 )
34+import Control.Monad.Fix (MonadFix)
37import Data.Binary
35import Data.Binary
38import Data.Typeable (Typeable)
36import Data.Typeable (Typeable)
39import GHC.Generics
37import GHC.Generics
@@ -60,8 +58,14 @@ data MxEvent =
60 -- ^ fired whenever a node /dies/ (i.e., the connection is broken/disconnected)
58 -- ^ fired whenever a node /dies/ (i.e., the connection is broken/disconnected)
61 | MxSent ProcessId ProcessId Message
59 | MxSent ProcessId ProcessId Message
62 -- ^ fired whenever a message is sent from a local process
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
63 | MxReceived ProcessId Message
65 | MxReceived ProcessId Message
64 -- ^ fired whenever a message is received by a local process
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
65 | MxConnected ConnectionId EndPointAddress
69 | MxConnected ConnectionId EndPointAddress
66 -- ^ fired when a network-transport connection is first established
70 -- ^ fired when a network-transport connection is first established
67 | MxDisconnected ConnectionId EndPointAddress
71 | MxDisconnected ConnectionId EndPointAddress
@@ -100,17 +104,10 @@ type Fork = (Process () -> IO ProcessId)
100newtype MxAgentId = MxAgentId { agentId :: String }
104newtype MxAgentId = MxAgentId { agentId :: String }
101 deriving (Typeable, Binary, Eq, Ord)
105 deriving (Typeable, Binary, Eq, Ord)
102
106
103−data MxTableId =
104− MxForAgent !MxAgentId
105− | MxForPid !ProcessId
106− deriving (Typeable, Generic)
107−instance Binary MxTableId where
108−
109data MxAgentState s = MxAgentState
107data MxAgentState s = MxAgentState
110 {
108 {
111 mxAgentId :: !MxAgentId
109 mxAgentId :: !MxAgentId
112 , mxBus :: !(TChan Message)
110 , mxBus :: !(TChan Message)
113− , mxSharedTable :: !ProcessId
114 , mxLocalState :: !s
111 , mxLocalState :: !s
115 }
112 }
116
113
@@ -123,19 +120,12 @@ newtype MxAgent s a =
123 } deriving ( Functor
120 } deriving ( Functor
124 , Monad
121 , Monad
125 , MonadIO
122 , MonadIO
123+ , MonadFix
126 , ST.MonadState (MxAgentState s)
124 , ST.MonadState (MxAgentState s)
127 , Typeable
125 , Typeable
128 , Applicative
126 , Applicative
129 )
127 )
130
128
131−data MxAgentStart = MxAgentStart
132− {
133− mxAgentTableChan :: SendPort ProcessId
134− , mxAgentIdStart :: MxAgentId
135− }
136− deriving (Typeable, Generic)
137−instance Binary MxAgentStart where
138−
139data ChannelSelector = InputChan | Mailbox
129data ChannelSelector = InputChan | Mailbox
140
130
141-- | Represents the actions a management agent can take
131-- | Represents the actions a management agent can take
src/Control/Distributed/Process/Node.hs+228−169
@@ -5,6 +5,7 @@
5{-# LANGUAGE BangPatterns #-}
5{-# LANGUAGE BangPatterns #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7{-# LANGUAGE RecordWildCards #-}
7{-# LANGUAGE RecordWildCards #-}
8+{-# LANGUAGE MagicHash #-}
8
9
9-- | Local nodes
10-- | Local nodes
10--
11--
@@ -20,10 +21,6 @@ module Control.Distributed.Process.Node
20
21
21-- 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.
22
23
23−#if ! MIN_VERSION_base(4,6,0)
24−import Prelude hiding (catch)
25−#endif
26−
27import System.IO (fixIO, hPutStrLn, stderr)
24import System.IO (fixIO, hPutStrLn, stderr)
28import System.Mem.Weak (Weak, deRefWeak)
25import System.Mem.Weak (Weak, deRefWeak)
29import qualified Data.ByteString.Lazy as BSL (fromChunks)
26import qualified Data.ByteString.Lazy as BSL (fromChunks)
@@ -31,28 +28,39 @@ import Data.Binary (decode)
31import Data.Map (Map)
28import Data.Map (Map)
32import qualified Data.Map as Map
29import qualified Data.Map as Map
33 ( empty
30 ( empty
31+ , delete
34 , toList
32 , toList
35 , fromList
33 , fromList
36− , filter
34+ , insert
35+ , lookup
36+ , partition
37 , partitionWithKey
37 , partitionWithKey
38 , elems
38 , elems
39 , size
39 , size
40 , filterWithKey
40 , filterWithKey
41 , foldlWithKey
41 , foldlWithKey
42 )
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
43import Data.Set (Set)
49import Data.Set (Set)
44import qualified Data.Set as Set
50import qualified Data.Set as Set
45 ( empty
51 ( empty
46 , insert
52 , insert
47 , delete
53 , delete
54+ , map
48 , member
55 , member
49 , toList
56 , toList
57+ , union
50 )
58 )
51import Data.Foldable (forM_)
59import Data.Foldable (forM_)
52import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
60import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
53import Data.Typeable (Typeable)
61import Data.Typeable (Typeable)
54import Control.Category ((>>>))
62import Control.Category ((>>>))
55−import Control.Applicative (Applicative, (<$>))
63+import Control.Applicative
56import Control.Monad (void, when, join)
64import Control.Monad (void, when, join)
57import Control.Monad.IO.Class (MonadIO, liftIO)
65import Control.Monad.IO.Class (MonadIO, liftIO)
58import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
66import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
@@ -60,11 +68,12 @@ import qualified Control.Monad.State.Strict as StateT (get, put)
60import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
68import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
61import Control.Exception
69import Control.Exception
62 ( throwIO
70 ( throwIO
63− , AsyncException(ThreadKilled)
64 , SomeException
71 , SomeException
65 , Exception
72 , Exception
66 , throwTo
73 , throwTo
67 , uninterruptibleMask_
74 , uninterruptibleMask_
75+ , getMaskingState
76+ , MaskingState(..)
68 )
77 )
69import qualified Control.Exception as Exception
78import qualified Control.Exception as Exception
70 ( Handler(..)
79 ( Handler(..)
@@ -72,12 +81,13 @@ import qualified Control.Exception as Exception
72 , catches
81 , catches
73 , finally
82 , finally
74 )
83 )
75−import Control.Concurrent (forkIO, forkIOWithUnmask, killThread, 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
76import Control.Distributed.Process.Internal.StrictMVar
87import Control.Distributed.Process.Internal.StrictMVar
77 ( newMVar
88 ( newMVar
78 , withMVar
89 , withMVar
79 , modifyMVarMasked
90 , modifyMVarMasked
80− , modifyMVar_
81 , modifyMVar
91 , modifyMVar
82 , newEmptyMVar
92 , newEmptyMVar
83 , putMVar
93 , putMVar
@@ -167,18 +177,17 @@ import Control.Distributed.Process.Internal.Types
167 , RegisterReply(..)
177 , RegisterReply(..)
168 , WhereIsReply(..)
178 , WhereIsReply(..)
169 , payloadToMessage
179 , payloadToMessage
170− , messageToPayload
171 , createUnencodedMessage
180 , createUnencodedMessage
181+ , unsafeCreateUnencodedMessage
172 , runLocalProcess
182 , runLocalProcess
173 , firstNonReservedProcessId
183 , firstNonReservedProcessId
174− , ImplicitReconnect(WithImplicitReconnect,NoImplicitReconnect)
184+ , ImplicitReconnect(WithImplicitReconnect)
175 )
185 )
176import Control.Distributed.Process.Management.Internal.Agent
186import Control.Distributed.Process.Management.Internal.Agent
177 ( mxAgentController
187 ( mxAgentController
178 )
188 )
179−import qualified Control.Distributed.Process.Management.Internal.Table as Table
189+import Control.Distributed.Process.Management.Internal.Types
180− ( mxTableCoordinator
190+ ( MxEvent(..)
181− , startTableCoordinator
182 )
191 )
183import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
192import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
184 ( remoteTable
193 ( remoteTable
@@ -192,14 +201,9 @@ import Control.Distributed.Process.Management.Internal.Trace.Types
192 , traceLogFmt
201 , traceLogFmt
193 , enableTrace
202 , enableTrace
194 )
203 )
195−import Control.Distributed.Process.Management.Internal.Types
196− ( MxEvent(..)
197− )
198import Control.Distributed.Process.Serializable (Serializable)
204import Control.Distributed.Process.Serializable (Serializable)
199import Control.Distributed.Process.Internal.Messaging
205import Control.Distributed.Process.Internal.Messaging
200 ( sendBinary
206 ( sendBinary
201− , sendMessage
202− , sendPayload
203 , closeImplicitReconnections
207 , closeImplicitReconnections
204 , impliesDeathOf
208 , impliesDeathOf
205 )
209 )
@@ -208,8 +212,11 @@ import Control.Distributed.Process.Internal.Primitives
208 , receiveWait
212 , receiveWait
209 , match
213 , match
210 , sendChan
214 , sendChan
211− , try
212 , unwrapMessage
215 , unwrapMessage
216+ , monitor
217+ , unmonitorAsync
218+ , getSelfNode
219+ , SayMessage(..)
213 )
220 )
214import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
221import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
215import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
222import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
@@ -218,7 +225,20 @@ import qualified Control.Distributed.Process.Internal.StrictContainerAccessors a
218 ( mapMaybe
225 ( mapMaybe
219 , mapDefault
226 , mapDefault
220 )
227 )
228+import Control.Monad.Catch (try)
229+import GHC.IO (IO(..), unsafeUnmask)
230+import GHC.Base ( maskAsyncExceptions# )
231+
221import 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
222
242
223--------------------------------------------------------------------------------
243--------------------------------------------------------------------------------
224-- Initialization --
244-- Initialization --
@@ -304,8 +324,6 @@ startServiceProcesses node = do
304 -- before /that/ process has started - this is a totally harmless race
324 -- before /that/ process has started - this is a totally harmless race
305 -- however, so we deliberably ignore it
325 -- however, so we deliberably ignore it
306 startDefaultTracer node
326 startDefaultTracer node
307− tableCoordinatorPid <- fork $ Table.startTableCoordinator fork
308− runProcess node $ register Table.mxTableCoordinator tableCoordinatorPid
309 logger <- forkProcess node loop
327 logger <- forkProcess node loop
310 runProcess node $ do
328 runProcess node $ do
311 register "logger" logger
329 register "logger" logger
@@ -314,12 +332,11 @@ startServiceProcesses node = do
314 -- process which uses 'send' or other primitives which are traced.
332 -- process which uses 'send' or other primitives which are traced.
315 register "trace.logger" logger
333 register "trace.logger" logger
316 where
334 where
317− fork = forkProcess node
318−
319 loop = do
335 loop = do
320 receiveWait
336 receiveWait
321− [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
337+ [ match $ \(SayMessage time pid string) -> do
322− liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
338+ let time' = formatTime defaultTimeLocale "%c" time
339+ liftIO . hPutStrLn stderr $ time' ++ " " ++ show pid ++ ": " ++ string
323 loop
340 loop
324 , match $ \((time, string) :: (String, String)) -> do
341 , match $ \((time, string) :: (String, String)) -> do
325 -- this is a 'trace' message from the local node tracer
342 -- this is a 'trace' message from the local node tracer
@@ -361,11 +378,14 @@ runProcess node proc = do
361
378
362-- | Spawn a new process on a local node
379-- | Spawn a new process on a local node
363forkProcess :: LocalNode -> Process () -> IO ProcessId
380forkProcess :: LocalNode -> Process () -> IO ProcessId
364−forkProcess node proc =
381+forkProcess node proc = do
365− modifyMVarMasked (localState node) startProcess
382+ ms <- getMaskingState
383+ modifyMVarMasked (localState node) (startProcess ms)
366 where
384 where
367− startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
385+ startProcess :: MaskingState
368− startProcess (LocalNodeValid vst) = do
386+ -> LocalNodeState
387+ -> IO (LocalNodeState, ProcessId)
388+ startProcess ms (LocalNodeValid vst) = do
369 let lpid = LocalProcessId { lpidCounter = vst ^. localPidCounter
389 let lpid = LocalProcessId { lpidCounter = vst ^. localPidCounter
370 , lpidUnique = vst ^. localPidUnique
390 , lpidUnique = vst ^. localPidUnique
371 }
391 }
@@ -387,7 +407,13 @@ forkProcess node proc =
387 , processThread = tid
407 , processThread = tid
388 , processNode = node
408 , processNode = node
389 }
409 }
390− tid' <- uninterruptibleMask_ $ forkIOWithUnmask $ \unmask -> do
410+ -- Rewrite this code when this is fixed:
411+ -- https://ghc.haskell.org/trac/ghc/ticket/10149
412+ let unmask = case ms of
413+ Unmasked -> unblock
414+ MaskedInterruptible -> block
415+ MaskedUninterruptible -> id
416+ tid' <- uninterruptibleMask_ $ forkIO $ do
391 reason <- Exception.catches
417 reason <- Exception.catches
392 (unmask $ runLocalProcess lproc proc >> return DiedNormal)
418 (unmask $ runLocalProcess lproc proc >> return DiedNormal)
393 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
419 [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
@@ -433,7 +459,7 @@ forkProcess node proc =
433 $ vst
459 $ vst
434 , pid
460 , pid
435 )
461 )
436− startProcess LocalNodeClosed =
462+ startProcess _ LocalNodeClosed =
437 throwIO $ NodeClosedException $ localNodeId node
463 throwIO $ NodeClosedException $ localNodeId node
438
464
439 cleanupProcess :: ProcessId
465 cleanupProcess :: ProcessId
@@ -466,7 +492,7 @@ type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
466data IncomingTarget =
492data IncomingTarget =
467 Uninit
493 Uninit
468 | ToProc ProcessId (Weak (CQueue Message))
494 | ToProc ProcessId (Weak (CQueue Message))
469− | ToChan TypedChannel
495+ | ToChan SendPortId TypedChannel
470 | ToNode
496 | ToNode
471
497
472data ConnectionState = ConnectionState {
498data ConnectionState = ConnectionState {
@@ -508,7 +534,8 @@ handleIncomingMessages node = go initConnectionState
508 . (incomingFrom theirAddr ^: Set.insert cid)
534 . (incomingFrom theirAddr ^: Set.insert cid)
509 $ st
535 $ st
510 )
536 )
511− else invalidRequest cid st
537+ else invalidRequest cid st $
538+ "attempt to connect with unsupported reliability " ++ show rel
512 NT.Received cid payload ->
539 NT.Received cid payload ->
513 case st ^. incomingAt cid of
540 case st ^. incomingAt cid of
514 Just (_, ToProc pid weakQueue) -> do
541 Just (_, ToProc pid weakQueue) -> do
@@ -520,13 +547,17 @@ handleIncomingMessages node = go initConnectionState
520 enqueue queue msg -- 'enqueue' is strict
547 enqueue queue msg -- 'enqueue' is strict
521 trace node (MxReceived pid msg)
548 trace node (MxReceived pid msg)
522 go st
549 go st
523− Just (_, ToChan (TypedChannel chan')) -> do
550+ Just (_, ToChan chId (TypedChannel chan')) -> do
524 mChan <- deRefWeak chan'
551 mChan <- deRefWeak chan'
525 -- 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
526 -- the channel and we simply ignore the incoming message
553 -- the channel and we simply ignore the incoming message
527− forM_ mChan $ \chan -> atomically $
554+ forM_ mChan $ \chan -> do
528− -- We make sure the message is fully decoded when it is enqueued
555+ msg' <- atomically $ do
529− writeTQueue chan $! decode (BSL.fromChunks payload)
556+ msg <- return $! decode (BSL.fromChunks payload)
557+ -- We make sure the message is fully decoded when it is enqueued
558+ writeTQueue chan msg
559+ return msg
560+ trace node $ MxReceivedPort chId $ unsafeCreateUnencodedMessage msg'
530 go st
561 go st
531 Just (_, ToNode) -> do
562 Just (_, ToNode) -> do
532 let ctrlMsg = decode . BSL.fromChunks $ payload
563 let ctrlMsg = decode . BSL.fromChunks $ payload
@@ -541,7 +572,9 @@ handleIncomingMessages node = go initConnectionState
541 Just proc ->
572 Just proc ->
542 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
573 go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
543 Nothing ->
574 Nothing ->
544− invalidRequest cid st
575+ -- incoming attempt to connect to unknown process - might
576+ -- be dead already
577+ go (incomingAt cid ^= Nothing $ st)
545 SendPortIdentifier chId -> do
578 SendPortIdentifier chId -> do
546 let lcid = sendPortLocalId chId
579 let lcid = sendPortLocalId chId
547 lpid = processLocalId (sendPortProcessId chId)
580 lpid = processLocalId (sendPortProcessId chId)
@@ -551,21 +584,30 @@ handleIncomingMessages node = go initConnectionState
551 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
584 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
552 case mChannel of
585 case mChannel of
553 Just channel ->
586 Just channel ->
554− go (incomingAt cid ^= Just (src, ToChan channel) $ st)
587+ go (incomingAt cid ^= Just (src, ToChan chId channel) $ st)
555 Nothing ->
588 Nothing ->
556− invalidRequest cid st
589+ invalidRequest cid st $
590+ "incoming attempt to connect to unknown channel of"
591+ ++ " process " ++ show (sendPortProcessId chId)
557 Nothing ->
592 Nothing ->
558− invalidRequest cid st
593+ -- incoming attempt to connect to channel of unknown
594+ -- process - might be dead already
595+ go (incomingAt cid ^= Nothing $ st)
559 NodeIdentifier nid ->
596 NodeIdentifier nid ->
560 if nid == localNodeId node
597 if nid == localNodeId node
561 then go (incomingAt cid ^= Just (src, ToNode) $ st)
598 then go (incomingAt cid ^= Just (src, ToNode) $ st)
562− 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
563 Nothing ->
604 Nothing ->
564 invalidRequest cid st
605 invalidRequest cid st
606+ "message received from an unknown connection"
565 NT.ConnectionClosed cid ->
607 NT.ConnectionClosed cid ->
566 case st ^. incomingAt cid of
608 case st ^. incomingAt cid of
567 Nothing ->
609 Nothing ->
568− invalidRequest cid st
610+ invalidRequest cid st "closed unknown connection"
569 Just (src, _) -> do
611 Just (src, _) -> do
570 trace node (MxDisconnected cid src)
612 trace node (MxDisconnected cid src)
571 go ( (incomingAt cid ^= Nothing)
613 go ( (incomingAt cid ^= Nothing)
@@ -596,14 +638,16 @@ handleIncomingMessages node = go initConnectionState
596 -- and we just give up
638 -- and we just give up
597 fail "Cloud Haskell fatal error: received unexpected multicast"
639 fail "Cloud Haskell fatal error: received unexpected multicast"
598
640
599− invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
641+ invalidRequest :: NT.ConnectionId -> ConnectionState -> String -> IO ()
600− invalidRequest cid st = do
642+ invalidRequest cid st msg = do
601 -- 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
602 -- 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
603 -- should close incoming connections (this requires a Transport layer
645 -- should close incoming connections (this requires a Transport layer
604 -- extension).
646 -- extension).
605− traceEventFmtIO node "" [(TraceStr " [network] invalid request: "),
647+ traceEventFmtIO node "" [ TraceStr $ " [network] invalid request"
606− (Trace cid)]
648+ ++ " (" ++ msg ++ "): "
649+ , (Trace cid)
650+ ]
607 go ( incomingAt cid ^= Nothing
651 go ( incomingAt cid ^= Nothing
608 $ st
652 $ st
609 )
653 )
@@ -626,9 +670,9 @@ runNodeController node =
626
670
627data NCState = NCState
671data NCState = NCState
628 { -- Mapping from remote processes to linked local processes
672 { -- Mapping from remote processes to linked local processes
629− _links :: !(Map Identifier (Set ProcessId))
673+ _links :: !(BiMultiMap Identifier ProcessId ())
630 -- Mapping from remote processes to monitoring local processes
674 -- Mapping from remote processes to monitoring local processes
631− , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
675+ , _monitors :: !(BiMultiMap Identifier ProcessId MonitorRef)
632 -- Process registry: names and where they live, mapped to the PIDs
676 -- Process registry: names and where they live, mapped to the PIDs
633 , _registeredHere :: !(Map String ProcessId)
677 , _registeredHere :: !(Map String ProcessId)
634 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
678 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
@@ -644,8 +688,8 @@ newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
644 )
688 )
645
689
646initNCState :: NCState
690initNCState :: NCState
647−initNCState = NCState { _links = Map.empty
691+initNCState = NCState { _links = BiMultiMap.empty
648− , _monitors = Map.empty
692+ , _monitors = BiMultiMap.empty
649 , _registeredHere = Map.empty
693 , _registeredHere = Map.empty
650 , _registeredOnNodes = Map.empty
694 , _registeredOnNodes = Map.empty
651 }
695 }
@@ -661,6 +705,33 @@ instance Show ProcessKillException where
661 show (ProcessKillException pid reason) =
705 show (ProcessKillException pid reason) =
662 "killed-by=" ++ show pid ++ ",reason=" ++ reason
706 "killed-by=" ++ show pid ++ ",reason=" ++ reason
663
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+
664--------------------------------------------------------------------------------
735--------------------------------------------------------------------------------
665-- Tracing/Debugging --
736-- Tracing/Debugging --
666--------------------------------------------------------------------------------
737--------------------------------------------------------------------------------
@@ -703,11 +774,7 @@ nodeController = do
703 -- [Unified: Table 7, rule nc_forward]
774 -- [Unified: Table 7, rule nc_forward]
704 case destNid (ctrlMsgSignal msg) of
775 case destNid (ctrlMsgSignal msg) of
705 Just nid' | nid' /= localNodeId node ->
776 Just nid' | nid' /= localNodeId node ->
706− liftIO $ sendBinary node
777+ ncSendToNode nid' msg
707− (ctrlMsgSender msg)
708− (NodeIdentifier nid')
709− WithImplicitReconnect
710− msg
711 _ ->
778 _ ->
712 return ()
779 return ()
713
780
@@ -728,8 +795,8 @@ nodeController = do
728 ncEffectRegister from label atnode pid force
795 ncEffectRegister from label atnode pid force
729 NCMsg (ProcessIdentifier from) (WhereIs label) ->
796 NCMsg (ProcessIdentifier from) (WhereIs label) ->
730 ncEffectWhereIs from label
797 ncEffectWhereIs from label
731− NCMsg (ProcessIdentifier from) (NamedSend label msg') ->
798+ NCMsg _ (NamedSend label msg') ->
732− ncEffectNamedSend from label msg'
799+ ncEffectNamedSend label msg'
733 NCMsg _ (UnreliableSend lpid msg') ->
800 NCMsg _ (UnreliableSend lpid msg') ->
734 ncEffectLocalSend node (ProcessId (localNodeId node) lpid) msg'
801 ncEffectLocalSend node (ProcessId (localNodeId node) lpid) msg'
735 NCMsg _ (LocalSend to msg') ->
802 NCMsg _ (LocalSend to msg') ->
@@ -745,8 +812,7 @@ nodeController = do
745 NCMsg _ SigShutdown ->
812 NCMsg _ SigShutdown ->
746 liftIO $ do
813 liftIO $ do
747 NT.closeEndPoint (localEndPoint node)
814 NT.closeEndPoint (localEndPoint node)
748− `Exception.finally` throwIO ThreadKilled
815+ `Exception.finally` throwIO (NodeClosedException $ localNodeId node)
749− -- ThreadKilled seems to make more sense than fail/error here
750 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
816 NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
751 ncEffectGetNodeStats from nid
817 ncEffectGetNodeStats from nid
752 unexpected ->
818 unexpected ->
@@ -766,22 +832,18 @@ ncEffectMonitor from them mRef = do
766 case (shouldLink, isLocal node (ProcessIdentifier from)) of
832 case (shouldLink, isLocal node (ProcessIdentifier from)) of
767 (True, _) -> -- [Unified: first rule]
833 (True, _) -> -- [Unified: first rule]
768 case mRef of
834 case mRef of
769− Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
835+ Just ref -> modify' $ monitors ^: BiMultiMap.insert them from ref
770− Nothing -> modify' $ linksFor them ^: Set.insert from
836+ Nothing -> modify' $ links ^: BiMultiMap.insert them from ()
771 (False, True) -> -- [Unified: second rule]
837 (False, True) -> -- [Unified: second rule]
772 notifyDied from them DiedUnknownId mRef
838 notifyDied from them DiedUnknownId mRef
773 (False, False) -> -- [Unified: third rule]
839 (False, False) -> -- [Unified: third rule]
774 -- TODO: this is the right sender according to the Unified semantics,
840 -- TODO: this is the right sender according to the Unified semantics,
775 -- but perhaps having 'them' as the sender would make more sense
841 -- but perhaps having 'them' as the sender would make more sense
776 -- (see also: notifyDied)
842 -- (see also: notifyDied)
777− liftIO $ sendBinary node
843+ ncSendToNode (processNodeId from) $ NCMsg
778− (NodeIdentifier $ localNodeId node)
844+ { ctrlMsgSender = NodeIdentifier (localNodeId node)
779− (NodeIdentifier $ processNodeId from)
845+ , ctrlMsgSignal = Died them DiedUnknownId
780− WithImplicitReconnect
846+ }
781− NCMsg
782− { ctrlMsgSender = NodeIdentifier (localNodeId node)
783− , ctrlMsgSignal = Died them DiedUnknownId
784− }
785
847
786-- [Unified: Table 11]
848-- [Unified: Table 11]
787ncEffectUnlink :: ProcessId -> Identifier -> NC ()
849ncEffectUnlink :: ProcessId -> Identifier -> NC ()
@@ -795,7 +857,7 @@ ncEffectUnlink from them = do
795 postAsMessage from $ DidUnlinkNode nid
857 postAsMessage from $ DidUnlinkNode nid
796 SendPortIdentifier cid ->
858 SendPortIdentifier cid ->
797 postAsMessage from $ DidUnlinkPort cid
859 postAsMessage from $ DidUnlinkPort cid
798− modify' $ linksFor them ^: Set.delete from
860+ modify' $ links ^: BiMultiMap.delete them from ()
799
861
800-- [Unified: Table 11]
862-- [Unified: Table 11]
801ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
863ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
@@ -803,7 +865,7 @@ ncEffectUnmonitor from ref = do
803 node <- ask
865 node <- ask
804 when (isLocal node (ProcessIdentifier from)) $
866 when (isLocal node (ProcessIdentifier from)) $
805 postAsMessage from $ DidUnmonitor ref
867 postAsMessage from $ DidUnmonitor ref
806− modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
868+ modify' $ monitors ^: BiMultiMap.delete (monitorRefIdent ref) from ref
807
869
808-- [Unified: Table 12]
870-- [Unified: Table 12]
809ncEffectDied :: Identifier -> DiedReason -> NC ()
871ncEffectDied :: Identifier -> DiedReason -> NC ()
@@ -818,7 +880,7 @@ ncEffectDied ident reason = do
818 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
880 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
819
881
820 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
882 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
821− forM_ uss $ \us ->
883+ forM_ uss $ \(us, _) ->
822 when (localOnly <= isLocal node (ProcessIdentifier us)) $
884 when (localOnly <= isLocal node (ProcessIdentifier us)) $
823 notifyDied us them reason Nothing
885 notifyDied us them reason Nothing
824
886
@@ -827,9 +889,35 @@ ncEffectDied ident reason = do
827 when (localOnly <= isLocal node (ProcessIdentifier us)) $
889 when (localOnly <= isLocal node (ProcessIdentifier us)) $
828 notifyDied us them reason (Just ref)
890 notifyDied us them reason (Just ref)
829
891
830− modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
892+ -- Notify remote nodes that the process died so it can be removed from monitor
831−
893+ -- lists.
832− modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
894+ mapM_ (forwardDeath node) $
895+ [ nid | ProcessIdentifier pid <- [ident]
896+ , i <- Set.toList $ Set.union
897+ (Set.map fst $ BiMultiMap.lookupBy2nd pid unaffectedLinks)
898+ (Set.map fst $ BiMultiMap.lookupBy2nd pid unaffectedMons)
899+ , let nid = nodeOf i
900+ , nid /= localNodeId node
901+ ]
902+
903+ -- Delete monitors in the local node.
904+ let deleteDeads :: (Ord a, Ord v)
905+ => BiMultiMap a ProcessId v -> BiMultiMap a ProcessId v
906+ deleteDeads = case ident of
907+ -- deleteAllBy2nd is faster than partitionWithKeyBy2nd
908+ ProcessIdentifier pid -> BiMultiMap.deleteAllBy2nd pid
909+ _ -> snd . BiMultiMap.partitionWithKeyBy2nd
910+ (\pid _ -> ident `impliesDeathOf` ProcessIdentifier pid)
911+ unaffectedLinks' = deleteDeads unaffectedLinks
912+ unaffectedMons' = deleteDeads unaffectedMons
913+
914+ modify' $ (links ^= unaffectedLinks') . (monitors ^= unaffectedMons')
915+
916+ -- we now consider all labels for this identifier unregistered
917+ let toDrop pid = not $ ident `impliesDeathOf` ProcessIdentifier pid
918+ (keepNames, dropNames) <- Map.partition toDrop <$> gets (^. registeredHere)
919+ mapM_ (\(p, l) -> liftIO $ trace node (MxUnRegistered l p)) (Map.toList dropNames)
920+ modify' $ registeredHere ^= keepNames
833
921
834 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
922 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
835 mapM (\(pid,nidlist) ->
923 mapM (\(pid,nidlist) ->
@@ -837,20 +925,15 @@ ncEffectDied ident reason = do
837 True ->
925 True ->
838 do forM_ nidlist $ \(nid,_) ->
926 do forM_ nidlist $ \(nid,_) ->
839 when (not $ isLocal node (NodeIdentifier nid))
927 when (not $ isLocal node (NodeIdentifier nid))
840− (forwardNameDeath node nid)
928+ (forwardDeath node nid)
841 return Nothing
929 return Nothing
842 False -> return $ Just (pid,nidlist) )
930 False -> return $ Just (pid,nidlist) )
843 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
931 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
844 where
932 where
845− forwardNameDeath node nid =
933+ forwardDeath node nid = ncSendToNode nid
846− liftIO $ sendBinary node
934+ NCMsg { ctrlMsgSender = NodeIdentifier (localNodeId node)
847− (NodeIdentifier $ localNodeId node)
935+ , ctrlMsgSignal = Died ident reason
848− (NodeIdentifier $ nid)
936+ }
849− WithImplicitReconnect
850− NCMsg
851− { ctrlMsgSender = NodeIdentifier (localNodeId node)
852− , ctrlMsgSignal = Died ident reason
853− }
854
937
855-- [Unified: Table 13]
938-- [Unified: Table 13]
856ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
939ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
@@ -864,11 +947,7 @@ ncEffectSpawn pid cProc ref = do
864 Right p -> p
947 Right p -> p
865 node <- ask
948 node <- ask
866 pid' <- liftIO $ forkProcess node proc
949 pid' <- liftIO $ forkProcess node proc
867− liftIO $ sendMessage node
950+ ncSendToProcess pid $ unsafeCreateUnencodedMessage $ DidSpawn ref pid'
868− (NodeIdentifier (localNodeId node))
869− (ProcessIdentifier pid)
870− WithImplicitReconnect
871− (DidSpawn ref pid')
872
951
873-- Unified semantics does not explicitly describe how to implement 'register',
952-- Unified semantics does not explicitly describe how to implement 'register',
874-- but mentions it's "very similar to nsend" (Table 14)
953-- but mentions it's "very similar to nsend" (Table 14)
@@ -890,14 +969,15 @@ ncEffectRegister from label atnode mPid reregistration = do
890 do modify' $ registeredHereFor label ^= mPid
969 do modify' $ registeredHereFor label ^= mPid
891 updateRemote node currentVal mPid
970 updateRemote node currentVal mPid
892 case mPid of
971 case mPid of
893− (Just p) -> liftIO $ trace node (MxRegistered p label)
972+ (Just p) -> do
973+ if reregistration
974+ then liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
975+ else return ()
976+ liftIO $ trace node (MxRegistered p label)
894 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
977 Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
895 newVal <- gets (^. registeredHereFor label)
978 newVal <- gets (^. registeredHereFor label)
896− liftIO $ sendMessage node
979+ ncSendToProcess from $ unsafeCreateUnencodedMessage $
897− (NodeIdentifier (localNodeId node))
980+ RegisterReply label isOk newVal
898− (ProcessIdentifier from)
899− WithImplicitReconnect
900− (RegisterReply label isOk newVal)
901 else let operation =
981 else let operation =
902 case reregistration of
982 case reregistration of
903 True -> flip decList
983 True -> flip decList
@@ -927,46 +1007,36 @@ ncEffectRegister from label atnode mPid reregistration = do
927 decList (x:xs) tag = x:decList xs tag
1007 decList (x:xs) tag = x:decList xs tag
928 forward node to reg =
1008 forward node to reg =
929 when (not $ isLocal node (NodeIdentifier to)) $
1009 when (not $ isLocal node (NodeIdentifier to)) $
930− liftIO $ sendBinary node
1010+ ncSendToNode to $ NCMsg { ctrlMsgSender = ProcessIdentifier from
931− (ProcessIdentifier from)
1011+ , ctrlMsgSignal = reg
932− (NodeIdentifier to)
1012+ }
933− WithImplicitReconnect
934− NCMsg
935− { ctrlMsgSender = ProcessIdentifier from
936− , ctrlMsgSignal = reg
937− }
938
1013
939
1014
940-- Unified semantics does not explicitly describe 'whereis'
1015-- Unified semantics does not explicitly describe 'whereis'
941ncEffectWhereIs :: ProcessId -> String -> NC ()
1016ncEffectWhereIs :: ProcessId -> String -> NC ()
942ncEffectWhereIs from label = do
1017ncEffectWhereIs from label = do
943− node <- ask
944 mPid <- gets (^. registeredHereFor label)
1018 mPid <- gets (^. registeredHereFor label)
945− liftIO $ sendMessage node
1019+ ncSendToProcess from $ unsafeCreateUnencodedMessage $ WhereIsReply label mPid
946− (NodeIdentifier (localNodeId node))
947− (ProcessIdentifier from)
948− WithImplicitReconnect
949− (WhereIsReply label mPid)
950
1020
951-- [Unified: Table 14]
1021-- [Unified: Table 14]
952−ncEffectNamedSend :: ProcessId -> String -> Message -> NC ()
1022+ncEffectNamedSend :: String -> Message -> NC ()
953−ncEffectNamedSend from label msg = do
1023+ncEffectNamedSend label msg = do
954 mPid <- gets (^. registeredHereFor label)
1024 mPid <- gets (^. registeredHereFor label)
955− node <- ask
956 -- 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)
957− forM_ mPid $ \pid ->
1026+ forM_ mPid $ \to ->
958− liftIO $ sendPayload node
1027+ -- If this is a trace message we don't trace it to avoid entering a loop
959− (ProcessIdentifier from)
1028+ -- where trace messages produce more trace messages.
960− (ProcessIdentifier pid)
1029+ ncSendToProcessAndTrace (label /= "trace.logger") to msg
961− NoImplicitReconnect
962− (messageToPayload msg)
963
1030
964-- [Issue #DP-20]
1031-- [Issue #DP-20]
965ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
1032ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
966−ncEffectLocalSend node to msg =
1033+ncEffectLocalSend = ncEffectLocalSendAndTrace True
1034+
1035+ncEffectLocalSendAndTrace :: Bool -> LocalNode -> ProcessId -> Message -> NC ()
1036+ncEffectLocalSendAndTrace shouldTrace node to msg =
967 liftIO $ withLocalProc node to $ \p -> do
1037 liftIO $ withLocalProc node to $ \p -> do
968 enqueue (processQueue p) msg
1038 enqueue (processQueue p) msg
969− trace node (MxReceived to msg)
1039+ when shouldTrace $ trace node (MxReceived to msg)
970
1040
971-- [Issue #DP-20]
1041-- [Issue #DP-20]
972ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
1042ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
@@ -984,11 +1054,12 @@ ncEffectLocalPortSend from msg = do
984 -- 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
985 -- the channel and we simply ignore the incoming message - this
1055 -- the channel and we simply ignore the incoming message - this
986 ch <- deRefWeak chan'
1056 ch <- deRefWeak chan'
987− forM_ ch $ \chan -> deliverChan msg chan
1057+ forM_ ch $ \chan -> deliverChan node from msg chan
988− where deliverChan :: forall a . Message -> TQueue a -> IO ()
1058+ where deliverChan :: forall a . LocalNode -> SendPortId -> Message -> TQueue a -> IO ()
989− deliverChan (UnencodedMessage _ raw) chan' =
1059+ deliverChan n p (UnencodedMessage _ raw) chan' = do
990 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
1060 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
991− deliverChan (EncodedMessage _ _) _ =
1061+ trace n (MxReceivedPort p $ unsafeCreateUnencodedMessage raw)
1062+ deliverChan _ _ (EncodedMessage _ _) _ =
992 -- this will not happen unless someone screws with Primitives.hs
1063 -- this will not happen unless someone screws with Primitives.hs
993 error "invalid local channel delivery"
1064 error "invalid local channel delivery"
994
1065
@@ -1017,37 +1088,32 @@ ncEffectGetInfo from pid =
1017 $ return . (^. localProcessWithId lpid)
1088 $ return . (^. localProcessWithId lpid)
1018 case mProc of
1089 case mProc of
1019 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
1090 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
1020− from node (ProcessInfoNone DiedUnknownId)
1091+ from (ProcessInfoNone DiedUnknownId)
1021− Just proc -> do
1092+ Just proc -> do
1022− itsLinks <- gets (^. linksFor them)
1093+ itsLinks <- Set.map fst . BiMultiMap.lookupBy1st them <$>
1023− itsMons <- gets (^. monitorsFor them)
1094+ gets (^. links)
1095+ itsMons <- BiMultiMap.lookupBy1st them <$> gets (^. monitors)
1024 registered <- gets (^. registeredHere)
1096 registered <- gets (^. registeredHere)
1025 size <- liftIO $ queueSize $ processQueue $ proc
1097 size <- liftIO $ queueSize $ processQueue $ proc
1026
1098
1027 let reg = registeredNames registered
1099 let reg = registeredNames registered
1028 dispatch (isLocal node (ProcessIdentifier from))
1100 dispatch (isLocal node (ProcessIdentifier from))
1029 from
1101 from
1030− node
1031 ProcessInfo {
1102 ProcessInfo {
1032 infoNode = (processNodeId pid)
1103 infoNode = (processNodeId pid)
1033 , infoRegisteredNames = reg
1104 , infoRegisteredNames = reg
1034 , infoMessageQueueLength = size
1105 , infoMessageQueueLength = size
1035− , infoMonitors = Set.toList itsMons
1106+ , infoMonitors = Set.toList itsMons
1036− , infoLinks = Set.toList itsLinks
1107+ , infoLinks = Set.toList itsLinks
1037 }
1108 }
1038− where dispatch :: (Serializable a, Show a)
1109+ where dispatch :: (Serializable a)
1039 => Bool
1110 => Bool
1040 -> ProcessId
1111 -> ProcessId
1041− -> LocalNode
1042 -> a
1112 -> a
1043 -> NC ()
1113 -> NC ()
1044− dispatch True dest _ pInfo = postAsMessage dest $ pInfo
1114+ dispatch True dest pInfo = postAsMessage dest $ pInfo
1045− dispatch False dest node pInfo = do
1115+ dispatch False dest pInfo =
1046− liftIO $ sendMessage node
1116+ ncSendToProcess dest $ unsafeCreateUnencodedMessage pInfo
1047− (NodeIdentifier (localNodeId node))
1048− (ProcessIdentifier dest)
1049− WithImplicitReconnect
1050− pInfo
1051
1117
1052 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1118 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
1053 then (k:ks)
1119 then (k:ks)
@@ -1062,8 +1128,8 @@ ncEffectGetNodeStats from _nid = do
1062 NodeStats {
1128 NodeStats {
1063 nodeStatsNode = localNodeId node
1129 nodeStatsNode = localNodeId node
1064 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1130 , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1065− , nodeStatsMonitors = Map.size $ ncState ^. monitors
1131+ , nodeStatsMonitors = BiMultiMap.size $ ncState ^. monitors
1066− , nodeStatsLinks = Map.size $ ncState ^. links
1132+ , nodeStatsLinks = BiMultiMap.size $ ncState ^. links
1067 , nodeStatsProcesses = Map.size (nodeState ^. localProcesses)
1133 , nodeStatsProcesses = Map.size (nodeState ^. localProcesses)
1068 }
1134 }
1069 postAsMessage from stats
1135 postAsMessage from stats
@@ -1094,14 +1160,10 @@ notifyDied dest src reason mRef = do
1094 throwException dest $ PortLinkException pid reason
1160 throwException dest $ PortLinkException pid reason
1095 (False, _, _) ->
1161 (False, _, _) ->
1096 -- The change in sender comes from [Unified: Table 10]
1162 -- The change in sender comes from [Unified: Table 10]
1097− liftIO $ sendBinary node
1163+ ncSendToNode (processNodeId dest) $ NCMsg
1098− (NodeIdentifier $ localNodeId node)
1164+ { ctrlMsgSender = NodeIdentifier (localNodeId node)
1099− (NodeIdentifier $ processNodeId dest)
1165+ , ctrlMsgSignal = Died src reason
1100− WithImplicitReconnect
1166+ }
1101− NCMsg
1102− { ctrlMsgSender = NodeIdentifier (localNodeId node)
1103− , ctrlMsgSignal = Died src reason
1104− }
1105
1167
1106-- | [Unified: Table 8]
1168-- | [Unified: Table 8]
1107destNid :: ProcessSignal -> Maybe NodeId
1169destNid :: ProcessSignal -> Maybe NodeId
@@ -1172,25 +1234,26 @@ throwException :: Exception e => ProcessId -> e -> NC ()
1172throwException pid e = do
1234throwException pid e = do
1173 node <- ask
1235 node <- ask
1174 -- throwTo blocks until the exception is received by the target thread.
1236 -- throwTo blocks until the exception is received by the target thread.
1175− -- We fork a helper thread to avoid blocking the node controller.
1237+ -- We cannot easily make it happen asynchronpusly because then 'unlink'
1176− liftIO $ withLocalProc node pid $ \p -> void $ forkIO $ throwTo (processThread p) e
1238+ -- semantics would break.
1239+ liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1177
1240
1178withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1241withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1179withLocalProc node pid p =
1242withLocalProc node pid p =
1180 -- By [Unified: table 6, rule missing_process] messages to dead processes
1243 -- By [Unified: table 6, rule missing_process] messages to dead processes
1181 -- can silently be dropped
1244 -- can silently be dropped
1182 let lpid = processLocalId pid in do
1245 let lpid = processLocalId pid in do
1183− withValidLocalState node $ \vst ->
1246+ join $ withValidLocalState node $ \vst ->
1184− forM_ (vst ^. localProcessWithId lpid) p
1247+ return $ forM_ (vst ^. localProcessWithId lpid) p
1185
1248
1186--------------------------------------------------------------------------------
1249--------------------------------------------------------------------------------
1187-- Accessors --
1250-- Accessors --
1188--------------------------------------------------------------------------------
1251--------------------------------------------------------------------------------
1189
1252
1190−links :: Accessor NCState (Map Identifier (Set ProcessId))
1253+links :: Accessor NCState (BiMultiMap Identifier ProcessId ())
1191links = accessor _links (\ls st -> st { _links = ls })
1254links = accessor _links (\ls st -> st { _links = ls })
1192
1255
1193−monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1256+monitors :: Accessor NCState (BiMultiMap Identifier ProcessId MonitorRef)
1194monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1257monitors = accessor _monitors (\ms st -> st { _monitors = ms })
1195
1258
1196registeredHere :: Accessor NCState (Map String ProcessId)
1259registeredHere :: Accessor NCState (Map String ProcessId)
@@ -1199,12 +1262,6 @@ registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry }
1199registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1262registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
1200registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1263registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
1201
1264
1202−linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1203−linksFor ident = links >>> DAC.mapDefault Set.empty ident
1204−
1205−monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1206−monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1207−
1208registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1265registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
1209registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1266registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
1210
1267
@@ -1231,10 +1288,12 @@ registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
1231-- * the notifications for typed channels to that process.
1288-- * the notifications for typed channels to that process.
1232--
1289--
1233-- 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.
1234−splitNotif :: Identifier
1291+splitNotif :: (Ord a, Ord v)
1235− -> Map Identifier a
1292+ => Identifier
1236− -> (Map Identifier a, Map Identifier a)
1293+ -> BiMultiMap Identifier a v
1237−splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1294+ -> (Map Identifier (Set (a,v)), BiMultiMap Identifier a v)
1295+splitNotif ident =
1296+ BiMultiMap.partitionWithKeyBy1st (\k !_v -> ident `impliesDeathOf` k)
1238
1297
1239--------------------------------------------------------------------------------
1298--------------------------------------------------------------------------------
1240-- Auxiliary --
1299-- Auxiliary --
src/Control/Distributed/Process/Serializable.hs+6−18
@@ -1,8 +1,8 @@
1{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2{-# LANGUAGE UndecidableInstances #-}
2{-# LANGUAGE UndecidableInstances #-}
3{-# LANGUAGE FlexibleInstances #-}
3{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
4{-# LANGUAGE GADTs #-}
5{-# LANGUAGE GADTs #-}
5−{-# LANGUAGE CPP #-}
6module Control.Distributed.Process.Serializable
6module Control.Distributed.Process.Serializable
7 ( Serializable
7 ( Serializable
8 , encodeFingerprint
8 , encodeFingerprint
@@ -17,25 +17,17 @@ module Control.Distributed.Process.Serializable
17
17
18import Data.Binary (Binary)
18import Data.Binary (Binary)
19
19
20−#if MIN_VERSION_base(4,7,0)
20+import Data.Typeable (Typeable, typeRepFingerprint, typeOf)
21−import Data.Typeable (Typeable)
22−import Data.Typeable.Internal (TypeRep(TypeRep), typeOf)
23−#else
24−import Data.Typeable (Typeable(..))
25−import Data.Typeable.Internal (TypeRep(TypeRep))
26−#endif
27
21
28import Numeric (showHex)
22import Numeric (showHex)
29import Control.Exception (throw)
23import Control.Exception (throw)
30import GHC.Fingerprint.Type (Fingerprint(..))
24import GHC.Fingerprint.Type (Fingerprint(..))
31import Data.ByteString (ByteString)
25import Data.ByteString (ByteString)
32import qualified Data.ByteString as BS
26import qualified Data.ByteString as BS
33−import qualified Data.ByteString.Internal as BSI ( unsafeCreate
27+import qualified Data.ByteString.Internal as BSI ( unsafeCreate, toForeignPtr )
34− , inlinePerformIO
35− , toForeignPtr
36− )
37import Foreign.Storable (pokeByteOff, peekByteOff, sizeOf)
28import Foreign.Storable (pokeByteOff, peekByteOff, sizeOf)
38import Foreign.ForeignPtr (withForeignPtr)
29import Foreign.ForeignPtr (withForeignPtr)
30+import System.IO.Unsafe (unsafePerformIO)
39
31
40-- | Reification of 'Serializable' (see "Control.Distributed.Process.Closure")
32-- | Reification of 'Serializable' (see "Control.Distributed.Process.Closure")
41data SerializableDict a where
33data SerializableDict a where
@@ -63,7 +55,7 @@ decodeFingerprint :: ByteString -> Fingerprint
63decodeFingerprint bs
55decodeFingerprint bs
64 | BS.length bs /= sizeOfFingerprint =
56 | BS.length bs /= sizeOfFingerprint =
65 throw $ userError "decodeFingerprint: Invalid length"
57 throw $ userError "decodeFingerprint: Invalid length"
66− | otherwise = BSI.inlinePerformIO $ do
58+ | otherwise = unsafePerformIO $ do
67 let (fp, offset, _) = BSI.toForeignPtr bs
59 let (fp, offset, _) = BSI.toForeignPtr bs
68 withForeignPtr fp $ \p -> peekByteOff p offset
60 withForeignPtr fp $ \p -> peekByteOff p offset
69
61
@@ -73,11 +65,7 @@ sizeOfFingerprint = sizeOf (undefined :: Fingerprint)
73
65
74-- | The fingerprint of the typeRep of the argument
66-- | The fingerprint of the typeRep of the argument
75fingerprint :: Typeable a => a -> Fingerprint
67fingerprint :: Typeable a => a -> Fingerprint
76−#if MIN_VERSION_base(4,8,0)
68+fingerprint = typeRepFingerprint . typeOf
77−fingerprint a = let TypeRep fp _ _ _ = typeOf a in fp
78−#else
79−fingerprint a = let TypeRep fp _ _ = typeOf a in fp
80−#endif
81
69
82-- | Show fingerprint (for debugging purposes)
70-- | Show fingerprint (for debugging purposes)
83showFingerprint :: Fingerprint -> ShowS
71showFingerprint :: Fingerprint -> ShowS
src/Control/Distributed/Process/UnsafePrimitives.hs+109−29
@@ -32,7 +32,24 @@
32-- the /normal/ strategy).
32-- the /normal/ strategy).
33--
33--
34-- 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. You have been warned!
35+-- behaviour of your application. In addition, messages passed between Cloud
36+-- Haskell processes are written to a tracing infrastructure on the local node,
37+-- to provide improved introspection and debugging facilities for complex actor
38+-- based systems. This module makes no attempt to force evaluation in these
39+-- cases either, thus evaluation problems in passed data structures could not
40+-- only crash your processes, but could also bring down critical internal
41+-- services on which the node relies to function correctly.
42+--
43+-- If you wish to repudiate such issues, you are advised to consider the use
44+-- of NFSerialisable in the distributed-process-extras package, which type
45+-- class brings NFData into scope along with Serializable, such that we can
46+-- force evaluation. Intended for use with modules such as this one, this
47+-- approach guarantees correct evaluatedness in terms of @NFData@. Please note
48+-- however, that we /cannot/ guarantee that an @NFData@ instance will behave the
49+-- same way as a @Binary@ one with regards evaluation, so it is still possible
50+-- to introduce unexpected behaviour by using /unsafe/ primitives in this way.
51+--
52+-- You have been warned!
36--
53--
37-- 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
38-- /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
@@ -45,6 +62,8 @@ module Control.Distributed.Process.UnsafePrimitives
45 send
62 send
46 , sendChan
63 , sendChan
47 , nsend
64 , nsend
65+ , nsendRemote
66+ , usend
48 , wrapMessage
67 , wrapMessage
49 ) where
68 ) where
50
69
@@ -53,9 +72,15 @@ import Control.Distributed.Process.Internal.Messaging
53 , sendBinary
72 , sendBinary
54 , sendCtrlMsg
73 , sendCtrlMsg
55 )
74 )
56−
75+import Control.Distributed.Process.Management.Internal.Types
76+ ( MxEvent(..)
77+ )
78+import Control.Distributed.Process.Management.Internal.Trace.Types
79+ ( traceEvent
80+ )
57import Control.Distributed.Process.Internal.Types
81import Control.Distributed.Process.Internal.Types
58 ( ProcessId(..)
82 ( ProcessId(..)
83+ , NodeId(..)
59 , LocalNode(..)
84 , LocalNode(..)
60 , LocalProcess(..)
85 , LocalProcess(..)
61 , Process(..)
86 , Process(..)
@@ -65,6 +90,7 @@ import Control.Distributed.Process.Internal.Types
65 , ImplicitReconnect(..)
90 , ImplicitReconnect(..)
66 , SendPortId(..)
91 , SendPortId(..)
67 , Message
92 , Message
93+ , createMessage
68 , sendPortProcessId
94 , sendPortProcessId
69 , unsafeCreateUnencodedMessage
95 , unsafeCreateUnencodedMessage
70 )
96 )
@@ -75,45 +101,99 @@ import Control.Monad.Reader (ask)
75
101
76-- | Named send to a process in the local registry (asynchronous)
102-- | Named send to a process in the local registry (asynchronous)
77nsend :: Serializable a => String -> a -> Process ()
103nsend :: Serializable a => String -> a -> Process ()
78−nsend label msg =
104+nsend label msg = do
79− sendCtrlMsg Nothing (NamedSend label (unsafeCreateUnencodedMessage msg))
105+ proc <- ask
106+ let us = processId proc
107+ let msg' = wrapMessage msg
108+ -- see [note: tracing]
109+ liftIO $ traceEvent (localEventBus (processNode proc))
110+ (MxSentToName label us msg')
111+ sendCtrlMsg Nothing (NamedSend label msg')
112+
113+-- | Named send to a process in a remote registry (asynchronous)
114+nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
115+nsendRemote nid label msg = do
116+ proc <- ask
117+ let us = processId proc
118+ let node = processNode proc
119+ if localNodeId node == nid
120+ then nsend label msg
121+ else
122+ let lbl = label ++ "@" ++ show nid in do
123+ -- see [note: tracing] NB: this is a remote call to another NC...
124+ liftIO $ traceEvent (localEventBus node)
125+ (MxSentToName lbl us (wrapMessage msg))
126+ sendCtrlMsg (Just nid) (NamedSend label (createMessage msg))
80
127
81-- | Send a message
128-- | Send a message
82send :: Serializable a => ProcessId -> a -> Process ()
129send :: Serializable a => ProcessId -> a -> Process ()
83send them msg = do
130send them msg = do
84 proc <- ask
131 proc <- ask
85 let node = localNodeId (processNode proc)
132 let node = localNodeId (processNode proc)
86− destNode = (processNodeId them) in do
133+ destNode = (processNodeId them)
87− case destNode == node of
134+ us = (processId proc)
88− True -> unsafeSendLocal them msg
135+ msg' = wrapMessage msg in do
89− False -> liftIO $ sendMessage (processNode proc)
136+ -- see [note: tracing]
90− (ProcessIdentifier (processId proc))
137+ liftIO $ traceEvent (localEventBus (processNode proc))
91− (ProcessIdentifier them)
138+ (MxSent them us msg')
92− NoImplicitReconnect
139+ if destNode == node
93− msg
140+ then sendCtrlMsg Nothing $ LocalSend them msg'
94− where
141+ else liftIO $ sendMessage (processNode proc)
95− unsafeSendLocal :: (Serializable a) => ProcessId -> a -> Process ()
142+ (ProcessIdentifier (processId proc))
96− unsafeSendLocal pid msg' =
143+ (ProcessIdentifier them)
97− sendCtrlMsg Nothing $ LocalSend pid (unsafeCreateUnencodedMessage msg')
144+ NoImplicitReconnect
145+ msg
146+
147+-- | Send a message unreliably.
148+--
149+-- Unlike 'send', this function is insensitive to 'reconnect'. It will
150+-- try to send the message regardless of the history of connection failures
151+-- between the nodes.
152+--
153+-- Message passing with 'usend' is ordered for a given sender and receiver
154+-- if the messages arrive at all.
155+--
156+usend :: Serializable a => ProcessId -> a -> Process ()
157+usend them msg = do
158+ proc <- ask
159+ let there = processNodeId them
160+ let (us, node) = (processId proc, processNode proc)
161+ let msg' = wrapMessage msg
162+ -- see [note: tracing]
163+ liftIO $ traceEvent (localEventBus node) (MxSent them us msg')
164+ if localNodeId (processNode proc) == there
165+ then sendCtrlMsg Nothing $ LocalSend them msg'
166+ else sendCtrlMsg (Just there) $ UnreliableSend (processLocalId them)
167+ (createMessage msg)
168+
169+-- [note: tracing]
170+-- Note that tracing writes to the local node's control channel, and this
171+-- module explicitly specifies to its clients that it does unsafe message
172+-- encoding. The same is true for the messages it puts onto the Management
173+-- event bus, however we do *not* want unevaluated thunks hitting the event
174+-- bus control thread. Hence the word /Unsafe/ in this module's name!
175+--
98
176
99-- | Send a message on a typed channel
177-- | Send a message on a typed channel
100sendChan :: Serializable a => SendPort a -> a -> Process ()
178sendChan :: Serializable a => SendPort a -> a -> Process ()
101sendChan (SendPort cid) msg = do
179sendChan (SendPort cid) msg = do
102 proc <- ask
180 proc <- ask
103− let node = localNodeId (processNode proc)
181+ let node = processNode proc
104− destNode = processNodeId (sendPortProcessId cid) in do
182+ pid = processId proc
105− case destNode == node of
183+ us = localNodeId node
106− True -> unsafeSendChanLocal cid msg
184+ them = processNodeId (sendPortProcessId cid)
107− False -> do
185+ msg' = wrapMessage msg in do
108− liftIO $ sendBinary (processNode proc)
186+ liftIO $ traceEvent (localEventBus node) (MxSentToPort pid cid msg')
109− (ProcessIdentifier (processId proc))
187+ if them == us
110− (SendPortIdentifier cid)
188+ then unsafeSendChanLocal cid msg' -- NB: we wrap to P.Message !!!
111− NoImplicitReconnect
189+ else liftIO $ sendBinary node
112− msg
190+ (ProcessIdentifier pid)
191+ (SendPortIdentifier cid)
192+ NoImplicitReconnect
193+ msg
113 where
194 where
114− unsafeSendChanLocal :: (Serializable a) => SendPortId -> a -> Process ()
195+ unsafeSendChanLocal :: SendPortId -> Message -> Process ()
115− unsafeSendChanLocal spId msg' =
196+ unsafeSendChanLocal p m = sendCtrlMsg Nothing $ LocalPortSend p m
116− sendCtrlMsg Nothing $ LocalPortSend spId (unsafeCreateUnencodedMessage msg')
117
197
118-- | Create an unencoded @Message@ for any @Serializable@ type.
198-- | Create an unencoded @Message@ for any @Serializable@ type.
119wrapMessage :: Serializable a => a -> Message
199wrapMessage :: Serializable a => a -> Message
stack-ghc-7.10.3.yamlnew file+21
@@ -0,0 +1,21 @@
1+resolver: nightly-2016-03-08
2+
3+packages:
4+- '.'
5+- distributed-process-tests/
6+- location:
7+ git: https://github.com/haskell-distributed/network-transport-tcp.git
8+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
9+ extra-dep: true
10+
11+extra-deps:
12+- rank1dynamic-0.4.0
13+- distributed-static-0.3.8
14+- network-transport-0.5.2
15+- network-transport-inmemory-0.5.2
16+- rematch-0.2.0.0
17+- exceptions-0.8.2.1 # test dependency
18+
19+flags:
20+ distributed-process-tests:
21+ tcp: true
stack-ghc-8.0.2.yamlnew file+23
@@ -0,0 +1,23 @@
1+resolver: lts-9.21
2+# added -location for network-transport-tcp-0.6.0
3+# since c7fd79 broke the client facing API for createTransport :/
4+
5+packages:
6+- '.'
7+- distributed-process-tests/
8+- location:
9+ git: https://github.com/haskell-distributed/network-transport-tcp.git
10+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
11+ extra-dep: true
12+
13+extra-deps:
14+- rank1dynamic-0.4.0
15+- distributed-static-0.3.8
16+- network-transport-0.5.2
17+- network-transport-inmemory-0.5.2
18+- rematch-0.2.0.0
19+# - exceptions-0.8.2.1 # test dependency
20+
21+flags:
22+ distributed-process-tests:
23+ tcp: true
stack-ghc-8.2.2.yamlnew file+21
@@ -0,0 +1,21 @@
1+resolver: lts-11.22
2+
3+packages:
4+- '.'
5+- distributed-process-tests/
6+- location:
7+ git: https://github.com/haskell-distributed/network-transport-tcp.git
8+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
9+ extra-dep: true
10+
11+extra-deps:
12+- rank1dynamic-0.4.0
13+- distributed-static-0.3.8
14+- network-transport-0.5.2
15+- network-transport-inmemory-0.5.2
16+- rematch-0.2.0.0
17+# - exceptions-0.8.2.1 # test dependency
18+
19+flags:
20+ distributed-process-tests:
21+ tcp: true
stack-ghc-8.4.4.yamlnew file+20
@@ -0,0 +1,20 @@
1+resolver: lts-12.18
2+
3+packages:
4+- '.'
5+- distributed-process-tests/
6+- location:
7+ git: https://github.com/haskell-distributed/network-transport-tcp.git
8+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
9+ extra-dep: true
10+
11+extra-deps:
12+- rank1dynamic-0.4.0
13+- distributed-static-0.3.8
14+- network-transport-0.5.2
15+- network-transport-inmemory-0.5.2
16+- rematch-0.2.0.0
17+
18+flags:
19+ distributed-process-tests:
20+ tcp: true
stack-nightly.yamlnew file+26
@@ -0,0 +1,26 @@
1+resolver: nightly
2+
3+packages:
4+- '.'
5+- distributed-process-tests/
6+- location:
7+ git: https://github.com/haskell-distributed/distributed-static.git
8+ commit: c51e6aadd3a44c044e96a8e986c11d3a94b45b11
9+ extra-dep: true
10+- location:
11+ git: https://github.com/haskell-distributed/network-transport-tcp.git
12+ commit: d58250e2388b86a1865ac4139373cbdc106e72e1
13+ extra-dep: true
14+- location:
15+ git: https://github.com/haskell-distributed/network-transport-inmemory.git
16+ commit: 0ce97924f15a8c1570b2355151834305c9bd2a28
17+ extra-dep: true
18+
19+extra-deps:
20+- rank1dynamic-0.4.0
21+- network-transport-0.5.2
22+- rematch-0.2.0.0
23+
24+flags:
25+ distributed-process-tests:
26+ tcp: true
stack.yaml+17−4
@@ -1,9 +1,22 @@
1−resolver: nightly-2015-10-09
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+
2packages:
5packages:
3- '.'
6- '.'
4- distributed-process-tests/
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+
5extra-deps:
13extra-deps:
6−- distributed-static-0.3.2.0
14+- rank1dynamic-0.4.0
7−- network-transport-inmemory-0.5.0
15+- distributed-static-0.3.8
8−- rank1dynamic-0.3.1.0
16+- network-transport-0.5.2
17+- network-transport-inmemory-0.5.2
9- rematch-0.2.0.0
18- rematch-0.2.0.0
19+
20+flags:
21+ distributed-process-tests:
22+ tcp: true