git.iko.soy/distributed-process
on master help sign in
67 files changed+9459−2982
since splitsnapshots
unifiedsplit
.gitignore+3−13 .travis.yml+42−13 CONTRIBUTING.md+1 ChangeLog+138 LICENSE+1 Makefile−37 README.md+7−10 REPOS−4 benchmarks/Channels.hs+6−4 benchmarks/Latency.hs+7−4 benchmarks/ProcessRing.hs+29−17 benchmarks/Spawns.hs+8−4 benchmarks/Throughput.hs+8−4 distributed-process-tests/.gitignore+4 distributed-process-tests/CONTRIBUTING.md+1 distributed-process-tests/LICENSE+31 distributed-process-tests/README.md+14 distributed-process-tests/Setup.hs+2 distributed-process-tests/distributed-process-tests.cabal+158 distributed-process-tests/src/Control/Distributed/Process/Tests/CH.hs+1879 distributed-process-tests/src/Control/Distributed/Process/Tests/Closure.hs+589 distributed-process-tests/src/Control/Distributed/Process/Tests/Internal/Utils.hs+243 distributed-process-tests/src/Control/Distributed/Process/Tests/Mx.hs+542 tests/TestReceive.hs → distributed-process-tests/src/Control/Distributed/Process/Tests/Receive.hs+22−36 tests/TestStats.hs → distributed-process-tests/src/Control/Distributed/Process/Tests/Stats.hs+31−64 distributed-process-tests/src/Control/Distributed/Process/Tests/Tracing.hs+431 distributed-process-tests/src/Network/Transport/Test.hs+12 distributed-process-tests/tests/runInMemory.hs+30 distributed-process-tests/tests/runTCP.hs+47 distributed-process.cabal+120−178 doc/semantics/CloudHaskellSemantics.tex+706 doc/semantics/Makefile+11 doc/semantics/references.bib+72 src/Control/Distributed/Process.hs+110−140 src/Control/Distributed/Process/Closure.hs+22 src/Control/Distributed/Process/Debug.hs+288 src/Control/Distributed/Process/Internal/BiMultiMap.hs+135 src/Control/Distributed/Process/Internal/CQueue.hs+72−15 src/Control/Distributed/Process/Internal/Closure/BuiltIn.hs+48−5 src/Control/Distributed/Process/Internal/Closure/Explicit.hs+147 src/Control/Distributed/Process/Internal/Closure/TH.hs+24−8 src/Control/Distributed/Process/Internal/Messaging.hs+63−25 src/Control/Distributed/Process/Internal/Primitives.hs+473−257 src/Control/Distributed/Process/Internal/Spawn.hs+165 src/Control/Distributed/Process/Internal/StrictMVar.hs+37−5 src/Control/Distributed/Process/Internal/Trace.hs−122 src/Control/Distributed/Process/Internal/Types.hs+253−51 src/Control/Distributed/Process/Internal/WeakTQueue.hs+9−3 src/Control/Distributed/Process/Management.hs+509 src/Control/Distributed/Process/Management/Internal/Agent.hs+157 src/Control/Distributed/Process/Management/Internal/Bus.hs+21 src/Control/Distributed/Process/Management/Internal/Trace/Primitives.hs+179 src/Control/Distributed/Process/Management/Internal/Trace/Remote.hs+61 src/Control/Distributed/Process/Management/Internal/Trace/Tracer.hs+354 src/Control/Distributed/Process/Management/Internal/Trace/Types.hs+159 src/Control/Distributed/Process/Management/Internal/Types.hs+141 src/Control/Distributed/Process/Node.hs+486−263 src/Control/Distributed/Process/Serializable.hs+18−8 src/Control/Distributed/Process/UnsafePrimitives.hs+200 stack-ghc-7.10.3.yaml+21 stack-ghc-8.0.2.yaml+23 stack-ghc-8.2.2.yaml+21 stack-ghc-8.4.4.yaml+20 stack-nightly.yaml+26 stack.yaml+22 tests/TestCH.hs−1213 tests/TestClosure.hs−479
.gitignore+3−13
@@ -1,14 +1,4 @@
1−tags
2−TAGS
31dist/
4−\#*#
5−*~
6−*.aux
7−*.hp
8−*.hi
9−*.o
10−*.prof
11−*.ps
12−*.swp
13−cabal-dev
14−.DS_Store
2+.cabal-sandbox
3+cabal.sandbox.config
4+.stack*
.travis.yml+42−13
@@ -1,14 +1,43 @@
1−language: haskell
2−script: "make ci"
1+language: c
2+
3+sudo: false
4+
5+matrix:
6+ include:
7+ - env: ARGS="--stack-yaml stack-ghc-7.10.3.yaml"
8+ addons: {apt: {packages: [libgmp-dev]}}
9+ - env: ARGS="--stack-yaml stack-ghc-8.2.2.yaml"
10+ addons: {apt: {packages: [libgmp-dev]}}
11+ - env: ARGS="--stack-yaml stack-ghc-8.4.4.yaml"
12+ addons: {apt: {packages: [libgmp-dev]}}
13+ - env: ARGS="--stack-yaml stack-nightly.yaml --resolver nightly"
14+ addons: {apt: {packages: [libgmp-dev]}}
15+
16+cache:
17+ directories:
18+ - $HOME/.stack
19+
20+before_install:
21+- export PATH=$HOME/.local/bin:$PATH;
22+ mkdir -p ~/.local/bin;
23+ travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack';
24+ stack --version
25+
26+install:
27+- stack $ARGS setup --no-terminal;
28+ stack $ARGS build --no-terminal --only-snapshot --test --no-run-tests;
29+
30+script:
31+- export ARG='--test-arguments'
32+ export TEST_PACKAGE="distributed-process-tests:"
33+- stack ${ARGS} test $ARG='--plain -t "!Flaky"' ${TEST_PACKAGE}TestCHInMemory
34+- stack ${ARGS} test $ARG='--plain -t "!Flaky"' ${TEST_PACKAGE}TestCHInTCP
35+- stack ${ARGS} test $ARG='--plain -t "!SpawnReconnect"' ${TEST_PACKAGE}TestClosure
36+- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestStats
37+- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestMxInMemory
38+- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestMxInTCP
39+- stack ${ARGS} test $ARG='--plain' ${TEST_PACKAGE}TestTracingInMemory
40+
341notifications:
4− email:
5− recipients:
6− - cloud.haskell@gmail.com
7− irc:
8− channels:
9− - "irc.freenode.org#haskell-distributed"
10− use_notice: true
11−branches:
12− only:
13− - master
14− - development
42+ slack:
43+ secure: LbqbNjK1KiHNiQo/i/Za4vkMD9l9mhmN+PWYFBQkNvGFWffSRayRKFrTqt4znV2p7h4dY1XZFoC7T5RemBVQLq2ppZAUWxkITeu1OUlnmVLDfJLeVYGTR/fn90nP6Y5ITE7T3A07nKmWaRbKpIgFBAOjzgqaM0csscDx3z0WW18=
CONTRIBUTING.mdnew file+1
@@ -0,0 +1 @@
1+See https://github.com/haskell-distributed/cloud-haskell/blob/master/CONTRIBUTING.md.
ChangeLog+138
@@ -1,3 +1,141 @@
1+[Unrealeased]
2+
3+*
4+
5+2018-06-12 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.4
6+
7+* Added support for exceptions >= 0.10
8+
9+2017-08-31 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.3
10+
11+* Drop support for ghc-7.8.* and earlier.
12+
13+2017-08-31 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.2
14+
15+* Fixed build errors with ghc-8.2.1.
16+
17+2017-08-22 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.1
18+
19+* Relax upper bounds in dependencies to build with ghc-8.2.1.
20+
21+2017-08-21 Facundo Domínguez <facundo.dominguez@tweag.io> 0.7.0
22+
23+* Change type of message sent by `say` from a 3-tuple to a proper
24+type (`SayMessage`) with a proper `UTCTime`. (#291)
25+* Expose the MonitorRef in the withMonitor call.
26+* Have unmonitor remove the monitor message in the inbox. (#268)
27+* Remove Mx Data Tables. This API isn't used, is easy to replace with various
28+other packages. (#276)
29+* Add Ord instance to SpawnRef.
30+
31+2016-10-13 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.6
32+
33+* Remove monitors from remote nodes when a process dies. (#295)
34+
35+2016-10-12 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.5
36+
37+* Use only one connection to communicate between NCs. (#296, #297)
38+* Improve documentation of CQueue.
39+* Implement bidirectional multimaps for links and monitors. (#293, #294)
40+* Fix various warnings. (#292)
41+* Fix some of the intermittent failures in tests.
42+* Improve error messages when node controllers receive invalid requests.
43+(#290)
44+
45+2016-06-09 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.4
46+
47+* Fixup build errors.
48+
49+2016-06-09 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.3
50+
51+* Relax template-haskell upper bound.
52+
53+2016-06-09 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.2
54+
55+* Provide compatibility with ghc-8.0.1
56+* Remove dependency on ghc-prim.
57+* Don't throw exceptions asynchronously.
58+* Bump upper bounds of dependencies.
59+* Fix exception handling in callLocal.
60+* Have spawnLocal inherit the masking state of the caller.
61+* Have nsend send unencoded messages to local processes.
62+
63+2016-03-03 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.1
64+
65+* Implement MonadCatch, MonadThrow, MonadMask for Process.
66+
67+2016-02-18 Facundo Domínguez <facundo.dominguez@tweag.io> 0.6.0
68+
69+* Have nsendRemote skip the transport for local communication.
70+* Unsafe primitives for usend and nsendRemote.
71+* Stop using the transport for local communication.
72+* Skip the transport for whereisRemoteAsync and registerRemoteAsync.
73+* Have nsendRemote skip the transport for local communication.
74+* Have runProcess forward exceptions.
75+* Reimport distributed-process-tests. d-p and d-p-test now can be kept in
76+sync.
77+* Add a stack.yaml file for building tests and d-p all at once conveniently.
78+* Implement unreliable forward (uforward).
79+* Add Functor instance for Match data type
80+* Have `spawnAsync` not use the transport in the local case.
81+* Fix monitor race in 'call'.
82+* Add compatibility with ghc-7.10: support new typeable, loosen deps, write
83+proper NFData instances, support new TH.
84+* Kill processes on a local node upon closeLocalNode.
85+* Fix getNodeStats function, see DP-97
86+* Return size of the queue in ProcessInfo.
87+* Implement unreliable send (usend).
88+* Implement MonadFix instance for Process.
89+* Introduce callLocal primitive.
90+* Prevent message loss due to timeouts in CQueue.
91+* More informative ProcessRegistrationException. Now includes the identifier
92+of the process that owns the name, if any.
93+* Avoid message loop between threads when tracing received messages.
94+
95+2015-06-15 Facundo Domínguez <facundo.dominguez@tweag.io> 0.5.5
96+
97+* Fix dependencies.
98+* Add compatibility with GHC-7.10.
99+* Fix various race conditions (DP-99, DP-103).
100+
101+2014-12-09 Tim Watson <watson.timothy@gmail.com> 0.5.2
102+
103+* Fix docstring for `register`
104+* Added Data instance to ProcessId, LocalProcessId and NodeId
105+* Add static serialiation dictionary for 'Static', for completeness
106+* Add Closure static serialization dictionary
107+* Replacement for modifyMVarMasked for GHC <= 7.4
108+* Document the use of built-in trace flags
109+* Make forkProcess exception-safe
110+* Make -Wall clean
111+
112+2014-08-13 Tim Watson <watson.timothy@gmail.com> 0.5.1
113+
114+* Fix cabal docs (thanks Markus Barenhoff)
115+* Expose lifted version of Control.Exception.mask_ (thanks Alexander Vershilov)
116+
117+2014-05-30 Tim Watson <watson.timothy@gmail.com> 0.5.0
118+
119+* Dependency on STM implicitly changed from 1.3 to 1.4, but was not reflected in the cabal file
120+* Race condition in local monitoring when using call
121+* mask now works correctly if unmask is called by another process
122+* Improve efficiency of local message passing
123+* nsend uses local communication channels
124+* Link Node Controller and Network Listener
125+* Label spawned processes using labelThread
126+* Relaxed upper bound on syb in the cabal manifest
127+* Bump binary version to include 0.7.*
128+* Exposed process info
129+* Exposed node statistics
130+* Moved tests to https://github.com/haskell-distributed/distributed-process-tests
131+* Added "polymorphic expect"
132+* Exposed Message type and broaden scope of polymorphic expect
133+* Added Management API (for working with internal/system events)
134+* Tracing can no longer be disabled
135+* We now report node statistics for monitoring/management
136+* Node.runProcess now propagates exceptions to its caller
137+* Added simple micro benchmarks
138+
11392013-01-27 Tim Watson <watson.timothy@gmail.com> 0.4.2
2140
3141* Improved exception handling for deferred type checked exit reasons
LICENSE+1
@@ -1,4 +1,5 @@
11Copyright Well-Typed LLP, 2011-2012
2+Copyright Tweag I/O Limited, 2015
23
34All rights reserved.
45
Makefiledeleted−37
@@ -1,37 +0,0 @@
1−# CI build
2−
3−GHC ?= $(shell which ghc)
4−CABAL ?= $(shell which cabal)
5−CABAL_DEV ?= $(shell which cabal-fav)
6−
7−BASE_GIT := git://github.com/haskell-distributed
8−REPOS=$(shell cat REPOS | sed '/^$$/d')
9−
10−.PHONY: all
11−all: dev-install
12−
13−.PHONY: dev-install
14−ifneq (,$(CABAL_DEV))
15−dev-install:
16− $(CABAL_DEV) install
17−else
18−dev-install:
19− $(error install cabal-dev to proceed)
20−endif
21−
22−.PHONY: ci
23−ci: travis-install travis-test
24−
25−.PHONY: travis-install
26−travis-install: $(REPOS)
27− $(CABAL) install --with-ghc=$(GHC) $(REPOS) --force-reinstalls
28− $(CABAL) install
29−
30−.PHONY: travis-test
31−travis-test:
32− $(CABAL) configure --enable-tests
33− $(CABAL) build
34− $(CABAL) test --show-details=always
35−
36−$(REPOS):
37− git clone $(BASE_GIT)/$@.git
README.md+7−10
@@ -1,19 +1,16 @@
1−### distributed-process
1+# distributed-process
2+[![travis](https://secure.travis-ci.org/haskell-distributed/distributed-process.png)](http://travis-ci.org/haskell-distributed/distributed-process)
3+[![Release](https://img.shields.io/hackage/v/distributed-process.svg)](https://hackage.haskell.org/package/distributed-process)
24
35This repository is part of Cloud Haskell.
46
57See http://haskell-distributed.github.com for documentation, user guides,
68tutorials and assistance.
79
8−### Getting Help / Raising Issues
10+## Getting Help / Raising Issues
911
10−Please visit our [bug tracker](http://cloud-haskell.atlassian.net) to submit
11−issues. Anyone can browse, although you'll need to provide an email address
12−and create an account in order to submit new issues.
12+Please visit the [bug tracker](https://github.com/haskell-distributed/distributed-process/issues) to submit issues. You can contact the distributed-haskell@googlegroups.com mailing list for help and comments.
1313
14−If you'd like to talk to a human, please contact us at the parallel-haskell
15−mailing list in the first instance - parallel-haskell@googlegroups.com.
14+## License
1615
17−### License
18−
19−distributed-process is made available under a BSD-3 license.
16+This package is made available under a 3-clause BSD-style license.
REPOSdeleted−4
@@ -1,4 +0,0 @@
1−rank1dynamic
2−distributed-static
3−network-transport
4−network-transport-tcp
benchmarks/Channels.hs+6−4
@@ -4,7 +4,7 @@ import Control.Monad
44import Control.Applicative
55import Control.Distributed.Process
66import Control.Distributed.Process.Node
7−import Network.Transport.TCP (createTransport, defaultTCPParameters)
7+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
88import Data.Binary (encode, decode)
99import qualified Data.ByteString.Lazy as BSL
1010
@@ -36,6 +36,8 @@ initialProcess "CLIENT" = do
3636main :: IO ()
3737main = do
3838 [role, host, port] <- getArgs
39− Right transport <- createTransport host port defaultTCPParameters
40− node <- newLocalNode transport initRemoteTable
41− runProcess node $ initialProcess role
39+ trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
40+ case trans of
41+ Right transport -> do node <- newLocalNode transport initRemoteTable
42+ runProcess node $ initialProcess role
43+ Left other -> error $ show other
benchmarks/Latency.hs+7−4
@@ -3,7 +3,7 @@ import Control.Monad
33import Control.Applicative
44import Control.Distributed.Process
55import Control.Distributed.Process.Node
6−import Network.Transport.TCP (createTransport, defaultTCPParameters)
6+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
77import Data.Binary (encode, decode)
88import qualified Data.ByteString.Lazy as BSL
99
@@ -31,6 +31,9 @@ initialProcess "CLIENT" = do
3131main :: IO ()
3232main = do
3333 [role, host, port] <- getArgs
34− Right transport <- createTransport host port defaultTCPParameters
35− node <- newLocalNode transport initRemoteTable
36− runProcess node $ initialProcess role
34+ trans <- createTransport
35+ (defaultTCPAddr host port) defaultTCPParameters
36+ case trans of
37+ Right transport -> do node <- newLocalNode transport initRemoteTable
38+ runProcess node $ initialProcess role
39+ Left other -> error $ show other
benchmarks/ProcessRing.hs+29−17
@@ -2,12 +2,17 @@
22
33To run the benchmarks, select a value for the ring size (sz) and
44the number of times to send a message around the ring
5+
56-}
7+
8+{-# LANGUAGE BangPatterns #-}
9+{-# LANGUAGE ScopedTypeVariables #-}
10+
611import Control.Monad
712import Control.Distributed.Process hiding (catch)
813import Control.Distributed.Process.Node
9−import Control.Exception (SomeException, catch)
10−import Network.Transport.TCP (createTransport, defaultTCPParameters)
14+import Control.Exception (catch, SomeException)
15+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
1116import System.Environment
1217import System.Console.GetOpt
1318
@@ -16,6 +21,7 @@ data Options = Options
1621 , optIterations :: Int
1722 , optForward :: Bool
1823 , optParallel :: Bool
24+ , optUnsafe :: Bool
1925 } deriving Show
2026
2127initialProcess :: Options -> Process ()
@@ -23,28 +29,30 @@ initialProcess op =
2329 let ringSz = optRingSize op
2430 msgCnt = optIterations op
2531 fwd = optForward op
32+ unsafe = optUnsafe op
2633 msg = ("foobar", "baz")
2734 in do
2835 self <- getSelfPid
29− ring <- makeRing fwd ringSz self
36+ ring <- makeRing fwd unsafe ringSz self
3037 forM_ [1..msgCnt] (\_ -> send ring msg)
3138 collect msgCnt
32− where relay' pid = do
39+ where relay fsend pid = do
3340 msg <- expect :: Process (String, String)
34− send pid msg
35− relay' pid
41+ fsend pid msg
42+ relay fsend pid
3643
3744 forward' pid =
3845 receiveWait [ matchAny (\m -> forward m pid) ] >> forward' pid
3946
40− makeRing :: Bool -> Int -> ProcessId -> Process ProcessId
41− makeRing !f !n !pid
42− | n == 0 = go f pid
43− | otherwise = go f pid >>= makeRing f (n - 1)
47+ makeRing :: Bool -> Bool -> Int -> ProcessId -> Process ProcessId
48+ makeRing !f !u !n !pid
49+ | n == 0 = go f u pid
50+ | otherwise = go f u pid >>= makeRing f u (n - 1)
4451
45− go :: Bool -> ProcessId -> Process ProcessId
46− go False next = spawnLocal $ relay' next
47− go True next = spawnLocal $ forward' next
52+ go :: Bool -> Bool -> ProcessId -> Process ProcessId
53+ go False False next = spawnLocal $ relay send next
54+ go False True next = spawnLocal $ relay unsafeSend next
55+ go True _ next = spawnLocal $ forward' next
4856
4957 collect :: Int -> Process ()
5058 collect !n
@@ -63,6 +71,7 @@ defaultOptions = Options
6371 , optIterations = 100
6472 , optForward = False
6573 , optParallel = False
74+ , optUnsafe = False
6675 }
6776
6877options :: [OptDescr (Options -> Options)]
@@ -72,8 +81,11 @@ options =
7281 , Option ['f'] ["forward"]
7382 (NoArg (\opts -> opts { optForward = True }))
7483 "use `forward' instead of send - default = False"
84+ , Option ['u'] ["unsafe-send"]
85+ (NoArg (\opts -> opts { optUnsafe = True }))
86+ "use 'unsafeSend' (ignored with -f) - default = False"
7587 , Option ['p'] ["parallel"]
76− (NoArg (\opts -> opts { optForward = True }))
88+ (NoArg (\opts -> opts { optParallel = True }))
7789 "send in parallel and consume sequentially - default = False"
7890 ]
7991
@@ -98,8 +110,8 @@ main = do
98110 argv <- getArgs
99111 (opt, _) <- parseArgv argv
100112 putStrLn $ "options: " ++ (show opt)
101− Right transport <- createTransport "127.0.0.1" "8090" defaultTCPParameters
113+ Right transport <- createTransport
114+ (defaultTCPAddr "127.0.0.1" "8090" ) defaultTCPParameters
102115 node <- newLocalNode transport initRemoteTable
103− catch (runProcess node $ initialProcess opt)
116+ catch (void $ runProcess node $ initialProcess opt)
104117 (\(e :: SomeException) -> putStrLn $ "ERROR: " ++ (show e))
105−
benchmarks/Spawns.hs+8−4
@@ -1,3 +1,5 @@
1+{-# LANGUAGE BangPatterns #-}
2+
13-- | Like Throughput, but send every ping from a different process
24-- (i.e., require a lightweight connection per ping)
35import System.Environment
@@ -5,7 +7,7 @@ import Control.Monad
57import Control.Applicative
68import Control.Distributed.Process
79import Control.Distributed.Process.Node
8−import Network.Transport.TCP (createTransport, defaultTCPParameters)
10+import Network.Transport.TCP (createTransport, defaultTCPAddr, defaultTCPParameters)
911import Data.Binary (encode, decode)
1012import qualified Data.ByteString.Lazy as BSL
1113
@@ -40,6 +42,8 @@ initialProcess "CLIENT" = do
4042main :: IO ()
4143main = do
4244 [role, host, port] <- getArgs
43− Right transport <- createTransport host port defaultTCPParameters
44− node <- newLocalNode transport initRemoteTable
45− runProcess node $ initialProcess role
45+ trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
46+ case trans of
47+ Right transport -> do node <- newLocalNode transport initRemoteTable
48+ runProcess node $ initialProcess role
49+ Left other -> error $ show other
benchmarks/Throughput.hs+8−4
@@ -1,10 +1,12 @@
1+{-# LANGUAGE BangPatterns #-}
12{-# LANGUAGE DeriveDataTypeable #-}
3+
24import System.Environment
35import Control.Monad
46import Control.Applicative
57import Control.Distributed.Process
68import Control.Distributed.Process.Node
7−import Network.Transport.TCP (createTransport, defaultTCPParameters)
9+import Network.Transport.TCP (createTransport, defaultTCPParameters, defaultTCPAddr)
810import Data.Binary
911import qualified Data.ByteString.Lazy as BSL
1012import Data.Typeable
@@ -65,6 +67,8 @@ initialProcess "CLIENT" = do
6567main :: IO ()
6668main = do
6769 [role, host, port] <- getArgs
68− Right transport <- createTransport host port defaultTCPParameters
69− node <- newLocalNode transport initRemoteTable
70− runProcess node $ initialProcess role
70+ trans <- createTransport (defaultTCPAddr host port) defaultTCPParameters
71+ case trans of
72+ Right transport -> do node <- newLocalNode transport initRemoteTable
73+ runProcess node $ initialProcess role
74+ Left other -> error $ show other
distributed-process-tests/.gitignorenew file+4
@@ -0,0 +1,4 @@
1+dist/
2+.cabal-sandbox
3+cabal.sandbox.config
4+.stack*
distributed-process-tests/CONTRIBUTING.mdnew file+1
@@ -0,0 +1 @@
1+See https://github.com/haskell-distributed/cloud-haskell/blob/master/CONTRIBUTING.md.
distributed-process-tests/LICENSEnew file+31
@@ -0,0 +1,31 @@
1+Copyright Well-Typed LLP, 2011-2012
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of the owner nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
distributed-process-tests/README.mdnew file+14
@@ -0,0 +1,14 @@
1+# distributed-process-tests
2+[![travis](https://secure.travis-ci.org/haskell-distributed/distributed-process-tests.png)](http://travis-ci.org/haskell-distributed/distributed-process-tests)
3+[![Release](https://img.shields.io/hackage/v/distributed-process-tests.svg)](https://hackage.haskell.org/package/distributed-process-tests)
4+
5+See http://haskell-distributed.github.com for documentation, user guides,
6+tutorials and assistance.
7+
8+## Getting Help / Raising Issues
9+
10+Please visit the [bug tracker](https://github.com/haskell-distributed/distributed-process-tests/issues) to submit issues. You can contact the distributed-haskell@googlegroups.com mailing list for help and comments.
11+
12+## License
13+
14+This package is made available under a 3-clause BSD-style license.
distributed-process-tests/Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
distributed-process-tests/distributed-process-tests.cabalnew file+158
@@ -0,0 +1,158 @@
1+name: distributed-process-tests
2+version: 0.4.11
3+synopsis: Tests and test support tools for distributed-process.
4+homepage: http://github.com/haskell-distributed/distributed-process/tree/master/distributed-process-tests
5+description: Tests and test suite for Cloud Haskell libraries.
6+license: BSD3
7+license-file: LICENSE
8+Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
9+Maintainer: Tim Watson <watson.timothy@gmail.com>
10+copyright: Well-Typed LLP
11+category: Control, Cloud Haskell
12+build-type: Simple
13+cabal-version: >=1.8
14+
15+flag tcp
16+ Description: build and run TCP tests
17+ Default: False
18+
19+library
20+ exposed-modules: Network.Transport.Test
21+ Control.Distributed.Process.Tests.CH
22+ Control.Distributed.Process.Tests.Closure
23+ Control.Distributed.Process.Tests.Mx
24+ Control.Distributed.Process.Tests.Receive
25+ Control.Distributed.Process.Tests.Stats
26+ Control.Distributed.Process.Tests.Tracing
27+ Control.Distributed.Process.Tests.Internal.Utils
28+ Build-Depends: base >= 4.4 && < 5,
29+ ansi-terminal >= 0.5,
30+ binary >= 0.5 && < 0.9,
31+ bytestring >= 0.9 && < 0.12,
32+ distributed-process >= 0.6.0 && < 0.8,
33+ distributed-static,
34+ exceptions >= 0.5,
35+ HUnit >= 1.2 && < 1.7,
36+ network-transport >= 0.4.1.0 && < 0.6,
37+ network >= 2.5 && < 2.9,
38+ random >= 1.0 && < 1.2,
39+ rematch >= 0.1.2.1,
40+ setenv >= 0.1.1.3,
41+ test-framework >= 0.6 && < 0.9,
42+ test-framework-hunit >= 0.2.0 && < 0.4,
43+ stm
44+ hs-source-dirs: src
45+ ghc-options: -Wall -fno-warn-unused-do-bind
46+ extensions: CPP,
47+ ExistentialQuantification,
48+ FlexibleInstances,
49+ DeriveDataTypeable,
50+ DeriveGeneric,
51+ GeneralizedNewtypeDeriving,
52+ RankNTypes,
53+ RecordWildCards,
54+ ScopedTypeVariables
55+ if impl(ghc <= 7.4.2)
56+ Build-Depends: ghc-prim == 0.2.0.0
57+
58+Test-Suite TestCHInMemory
59+ Type: exitcode-stdio-1.0
60+ Main-Is: runInMemory.hs
61+ CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.CH
62+ Build-Depends: base >= 4.4 && < 5,
63+ distributed-process-tests,
64+ network >= 2.3 && < 2.9,
65+ network-transport >= 0.4.1.0 && < 0.6,
66+ network-transport-inmemory >= 0.5,
67+ test-framework >= 0.6 && < 0.9
68+ Extensions: CPP
69+ ghc-options: -Wall -threaded -eventlog -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
70+ HS-Source-Dirs: tests
71+
72+Test-Suite TestCHInTCP
73+ Type: exitcode-stdio-1.0
74+ Main-Is: runTCP.hs
75+ CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.CH
76+ if flag(tcp)
77+ Build-Depends: base >= 4.4 && < 5,
78+ distributed-process-tests,
79+ network >= 2.3 && < 2.9,
80+ network-transport >= 0.4.1.0 && < 0.6,
81+ network-transport-tcp >= 0.5 && < 0.7,
82+ test-framework >= 0.6 && < 0.9
83+ else
84+ Buildable: False
85+ Extensions: CPP
86+ ghc-options: -Wall -threaded -eventlog -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
87+ HS-Source-Dirs: tests
88+
89+
90+Test-Suite TestClosure
91+ Type: exitcode-stdio-1.0
92+ Main-Is: runInMemory.hs
93+ CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Closure
94+ Build-Depends: base >= 4.4 && < 5,
95+ distributed-process-tests,
96+ network >= 2.3 && < 2.9,
97+ network-transport >= 0.4.1.0 && < 0.6,
98+ network-transport-inmemory >= 0.5,
99+ test-framework >= 0.6 && < 0.9
100+ Extensions: CPP
101+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
102+ HS-Source-Dirs: tests
103+
104+Test-Suite TestStats
105+ Type: exitcode-stdio-1.0
106+ Main-Is: runInMemory.hs
107+ CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Stats
108+ Build-Depends: base >= 4.4 && < 5,
109+ distributed-process-tests,
110+ network >= 2.3 && < 2.9,
111+ network-transport >= 0.4.1.0 && < 0.6,
112+ network-transport-inmemory >= 0.5,
113+ test-framework >= 0.6 && < 0.9
114+ Extensions: CPP
115+ ghc-options: -Wall -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
116+ HS-Source-Dirs: tests
117+
118+Test-Suite TestMxInMemory
119+ Type: exitcode-stdio-1.0
120+ Main-Is: runInMemory.hs
121+ CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Mx
122+ Build-Depends: base >= 4.4 && < 5,
123+ distributed-process-tests,
124+ network >= 2.3 && < 2.9,
125+ network-transport >= 0.4.1.0 && < 0.6,
126+ network-transport-inmemory >= 0.5,
127+ test-framework >= 0.6 && < 0.9
128+ Extensions: CPP
129+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
130+ HS-Source-Dirs: tests
131+
132+Test-Suite TestTracingInMemory
133+ Type: exitcode-stdio-1.0
134+ Main-Is: runInMemory.hs
135+ CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Tracing
136+ Build-Depends: base >= 4.4 && < 5,
137+ distributed-process-tests,
138+ network >= 2.3 && < 2.9,
139+ network-transport >= 0.4.1.0 && < 0.6,
140+ network-transport-inmemory >= 0.5,
141+ test-framework >= 0.6 && < 0.9
142+ Extensions: CPP
143+ ghc-options: -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
144+ HS-Source-Dirs: tests
145+
146+Test-Suite TestMxInTCP
147+ Type: exitcode-stdio-1.0
148+ Main-Is: runInMemory.hs
149+ CPP-Options: -DTEST_SUITE_MODULE=Control.Distributed.Process.Tests.Mx
150+ Build-Depends: base >= 4.4 && < 5,
151+ distributed-process-tests,
152+ network >= 2.3 && < 2.9,
153+ network-transport >= 0.4.1.0 && < 0.6,
154+ network-transport-inmemory >= 0.5,
155+ test-framework >= 0.6 && < 0.9
156+ Extensions: CPP
157+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
158+ HS-Source-Dirs: tests
distributed-process-tests/src/Control/Distributed/Process/Tests/CH.hsnew file+1879
@@ -0,0 +1,1879 @@
1+module Control.Distributed.Process.Tests.CH (tests) where
2+
3+#if ! MIN_VERSION_base(4,6,0)
4+import Prelude hiding (catch)
5+#endif
6+
7+import Network.Transport.Test (TestTransport(..))
8+
9+import Data.Binary (Binary(..))
10+import Data.Typeable (Typeable)
11+import Data.Foldable (forM_)
12+import Data.Function (fix)
13+import Data.IORef
14+ ( readIORef
15+ , writeIORef
16+ , newIORef
17+ )
18+import Control.Concurrent (forkIO, threadDelay, myThreadId, throwTo, ThreadId, yield)
19+import Control.Concurrent.MVar
20+ ( MVar
21+ , newEmptyMVar
22+ , putMVar
23+ , takeMVar
24+ , readMVar
25+ )
26+import Control.Monad (replicateM_, replicateM, forever, void, unless, join)
27+import Control.Exception (SomeException, throwIO, ErrorCall(..))
28+import Control.Monad.Catch (try, catch, finally, mask, onException)
29+import Control.Applicative ((<|>))
30+import qualified Network.Transport as NT (closeEndPoint, EndPointAddress)
31+import Control.Distributed.Process hiding
32+ ( try
33+ , catch
34+ , finally
35+ , mask
36+ , onException
37+ )
38+import Control.Distributed.Process.Internal.Types
39+ ( LocalNode(localEndPoint)
40+ , ProcessExitException(..)
41+ , nullProcessId
42+ , createUnencodedMessage
43+ )
44+import Control.Distributed.Process.Node
45+import Control.Distributed.Process.Debug
46+import Control.Distributed.Process.Management.Internal.Types
47+import Control.Distributed.Process.Tests.Internal.Utils (shouldBe, pause)
48+import Control.Distributed.Process.Serializable (Serializable)
49+import Data.Maybe (isNothing, isJust)
50+import Test.HUnit (Assertion, assertBool, assertFailure)
51+import Test.Framework (Test, testGroup)
52+import Test.Framework.Providers.HUnit (testCase)
53+import Control.Rematch hiding (match, isNothing, isJust)
54+import Control.Rematch.Run (Match(..))
55+
56+newtype Ping = Ping ProcessId
57+ deriving (Typeable, Binary, Show)
58+
59+newtype Pong = Pong ProcessId
60+ deriving (Typeable, Binary, Show)
61+
62+--------------------------------------------------------------------------------
63+-- Supporting definitions --
64+--------------------------------------------------------------------------------
65+
66+expectThat :: a -> Matcher a -> Assertion
67+expectThat a matcher = case res of
68+ MatchSuccess -> return ()
69+ (MatchFailure msg) -> assertFailure msg
70+ where res = runMatch matcher a
71+
72+-- | Like fork, but throw exceptions in the child thread to the parent
73+forkTry :: IO () -> IO ThreadId
74+forkTry p = do
75+ tid <- myThreadId
76+ forkIO $ catch p (\e -> throwTo tid (e :: SomeException))
77+
78+-- | The ping server from the paper
79+ping :: Process ()
80+ping = do
81+ Pong partner <- expect
82+ self <- getSelfPid
83+ send partner (Ping self)
84+ ping
85+
86+verifyClient :: String -> MVar Bool -> IO ()
87+verifyClient s b = takeMVar b >>= assertBool s
88+
89+expectPing :: MVar Bool -> Process ()
90+expectPing mv = expect >>= liftIO . putMVar mv . checkPing
91+ where
92+ checkPing (Ping _) = True
93+
94+-- | Quick and dirty synchronous version of whereisRemoteAsync
95+whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)
96+whereisRemote nid string = do
97+ whereisRemoteAsync nid string
98+ receiveWait [
99+ match (\(WhereIsReply _ mPid) -> return mPid)
100+ ]
101+
102+verifyWhereIsRemote :: NodeId -> String -> Process ProcessId
103+verifyWhereIsRemote n s = whereisRemote n s >>= maybe (die "remote name not found") return
104+
105+waitForExit :: ProcessId -> Process DiedReason
106+waitForExit pid = monitor pid >>= waitForDown
107+
108+waitForDown :: MonitorRef -> Process DiedReason
109+waitForDown ref =
110+ receiveWait [ matchIf (\(ProcessMonitorNotification ref' _ _) -> ref == ref')
111+ (\(ProcessMonitorNotification _ _ dr) -> return dr) ]
112+
113+syncBreakConnection :: (NT.EndPointAddress -> NT.EndPointAddress -> IO ()) -> LocalNode -> LocalNode -> IO ()
114+syncBreakConnection breakConnection nid0 nid1 = do
115+ m <- newEmptyMVar
116+ _ <- forkProcess nid1 $ getSelfPid >>= liftIO . putMVar m
117+ runProcess nid0 $ do
118+ them <- liftIO $ takeMVar m
119+ pinger <- spawnLocal $ forever $ send them ()
120+ _ <- monitorNode (localNodeId nid1)
121+ liftIO $ breakConnection (nodeAddress $ localNodeId nid0)
122+ (nodeAddress $ localNodeId nid1)
123+ NodeMonitorNotification _ _ _ <- expect
124+ kill pinger "finished"
125+ return ()
126+
127+data Add = Add ProcessId Double Double deriving (Typeable)
128+data Divide = Divide ProcessId Double Double deriving (Typeable)
129+data DivByZero = DivByZero deriving (Typeable)
130+
131+instance Binary Add where
132+ put (Add pid x y) = put pid >> put x >> put y
133+ get = Add <$> get <*> get <*> get
134+
135+instance Binary Divide where
136+ put (Divide pid x y) = put pid >> put x >> put y
137+ get = Divide <$> get <*> get <*> get
138+
139+instance Binary DivByZero where
140+ put DivByZero = return ()
141+ get = return DivByZero
142+
143+-- The math server from the paper
144+math :: Process ()
145+math = do
146+ receiveWait
147+ [ match (\(Add pid x y) -> send pid (x + y))
148+ , matchIf (\(Divide _ _ y) -> y /= 0)
149+ (\(Divide pid x y) -> send pid (x / y))
150+ , match (\(Divide pid _ _) -> send pid DivByZero)
151+ ]
152+ math
153+
154+-- | Monitor or link to a remote node
155+monitorOrLink :: Bool -- ^ 'True' for monitor, 'False' for link
156+ -> ProcessId -- ^ Process to monitor/link to
157+ -> Maybe (MVar ()) -- ^ MVar to signal on once the monitor has been set up
158+ -> Process (Maybe MonitorRef)
159+monitorOrLink mOrL pid mSignal = do
160+ result <- if mOrL then Just <$> monitor pid
161+ else link pid >> return Nothing
162+ -- Monitor is asynchronous, which usually does not matter but if we want a
163+ -- *specific* signal then it does. Therefore we wait until the MonitorRef is
164+ -- listed in the ProcessInfo and hope that this means the monitor has been set
165+ -- up.
166+ forM_ mSignal $ \signal -> do
167+ self <- getSelfPid
168+ spawnLocal $ do
169+ let waitForMOrL = do
170+ liftIO $ threadDelay 100000
171+ mpinfo <- getProcessInfo pid
172+ case mpinfo of
173+ Nothing -> waitForMOrL
174+ Just pinfo ->
175+ if mOrL then
176+ unless (result == lookup self (infoMonitors pinfo)) waitForMOrL
177+ else
178+ unless (elem self $ infoLinks pinfo) waitForMOrL
179+ waitForMOrL
180+ liftIO $ putMVar signal ()
181+ return result
182+
183+monitorTestProcess :: ProcessId -- Process to monitor/link to
184+ -> Bool -- 'True' for monitor, 'False' for link
185+ -> Bool -- Should we unmonitor?
186+ -> DiedReason -- Expected cause of death
187+ -> Maybe (MVar ()) -- Signal for 'monitor set up'
188+ -> MVar () -- Signal for successful termination
189+ -> Process ()
190+monitorTestProcess theirAddr mOrL un reason monitorSetup done =
191+ catch (do mRef <- monitorOrLink mOrL theirAddr monitorSetup
192+ case (un, mRef) of
193+ (True, Nothing) -> do
194+ unlink theirAddr
195+ liftIO $ putMVar done ()
196+ (True, Just ref) -> do
197+ unmonitor ref
198+ liftIO $ putMVar done ()
199+ (False, ref) -> do
200+ receiveWait [
201+ match (\(ProcessMonitorNotification ref' pid reason') -> do
202+ liftIO $ do
203+ assertBool "Bad Monitor Signal"
204+ (Just ref' == ref && pid == theirAddr &&
205+ mOrL && reason == reason')
206+ putMVar done ())
207+ ]
208+ )
209+ (\(ProcessLinkException pid reason') -> do
210+ (liftIO $ assertBool "link exception unmatched" $
211+ pid == theirAddr && not mOrL && not un && reason == reason')
212+ liftIO $ putMVar done ()
213+ )
214+
215+--------------------------------------------------------------------------------
216+-- The tests proper --
217+--------------------------------------------------------------------------------
218+
219+-- | Basic ping test
220+testPing :: TestTransport -> Assertion
221+testPing TestTransport{..} = do
222+ serverAddr <- newEmptyMVar
223+ clientDone <- newEmptyMVar
224+
225+ -- Server
226+ forkIO $ do
227+ localNode <- newLocalNode testTransport initRemoteTable
228+ addr <- forkProcess localNode ping
229+ putMVar serverAddr addr
230+
231+ -- Client
232+ forkIO $ do
233+ localNode <- newLocalNode testTransport initRemoteTable
234+ pingServer <- readMVar serverAddr
235+
236+ let numPings = 10000
237+
238+ runProcess localNode $ do
239+ pid <- getSelfPid
240+ replicateM_ numPings $ do
241+ send pingServer (Pong pid)
242+ p <- expectTimeout 3000000
243+ case p of
244+ Just (Ping _) -> return ()
245+ Nothing -> die "Failed to receive Ping"
246+
247+ putMVar clientDone ()
248+
249+ takeMVar clientDone
250+
251+-- | Monitor a process on an unreachable node
252+testMonitorUnreachable :: TestTransport -> Bool -> Bool -> Assertion
253+testMonitorUnreachable TestTransport{..} mOrL un = do
254+ deadProcess <- newEmptyMVar
255+ done <- newEmptyMVar
256+
257+ forkIO $ do
258+ localNode <- newLocalNode testTransport initRemoteTable
259+ addr <- forkProcess localNode expect
260+ closeLocalNode localNode
261+ putMVar deadProcess addr
262+
263+ forkIO $ do
264+ localNode <- newLocalNode testTransport initRemoteTable
265+ theirAddr <- readMVar deadProcess
266+ runProcess localNode $
267+ monitorTestProcess theirAddr mOrL un DiedDisconnect Nothing done
268+
269+ takeMVar done
270+
271+-- | Monitor a process which terminates normally
272+testMonitorNormalTermination :: TestTransport -> Bool -> Bool -> Assertion
273+testMonitorNormalTermination TestTransport{..} mOrL un = do
274+ monitorSetup <- newEmptyMVar
275+ monitoredProcess <- newEmptyMVar
276+ done <- newEmptyMVar
277+
278+ forkIO $ do
279+ localNode <- newLocalNode testTransport initRemoteTable
280+ addr <- forkProcess localNode $
281+ liftIO $ readMVar monitorSetup
282+ putMVar monitoredProcess addr
283+
284+ forkIO $ do
285+ localNode <- newLocalNode testTransport initRemoteTable
286+ theirAddr <- readMVar monitoredProcess
287+ runProcess localNode $
288+ monitorTestProcess theirAddr mOrL un DiedNormal (Just monitorSetup) done
289+
290+ takeMVar done
291+
292+-- | Monitor a process which terminates abnormally
293+testMonitorAbnormalTermination :: TestTransport -> Bool -> Bool -> Assertion
294+testMonitorAbnormalTermination TestTransport{..} mOrL un = do
295+ monitorSetup <- newEmptyMVar
296+ monitoredProcess <- newEmptyMVar
297+ done <- newEmptyMVar
298+
299+ let err = userError "Abnormal termination"
300+
301+ forkIO $ do
302+ localNode <- newLocalNode testTransport initRemoteTable
303+ addr <- forkProcess localNode . liftIO $ do
304+ readMVar monitorSetup
305+ throwIO err
306+ putMVar monitoredProcess addr
307+
308+ forkIO $ do
309+ localNode <- newLocalNode testTransport initRemoteTable
310+ theirAddr <- readMVar monitoredProcess
311+ runProcess localNode $
312+ monitorTestProcess theirAddr mOrL un (DiedException (show err)) (Just monitorSetup) done
313+
314+ takeMVar done
315+
316+-- | Monitor a local process that is already dead
317+testMonitorLocalDeadProcess :: TestTransport -> Bool -> Bool -> Assertion
318+testMonitorLocalDeadProcess TestTransport{..} mOrL un = do
319+ processAddr <- newEmptyMVar
320+ localNode <- newLocalNode testTransport initRemoteTable
321+ done <- newEmptyMVar
322+
323+ forkIO $ do
324+ addr <- forkProcess localNode $ return ()
325+ putMVar processAddr addr
326+
327+ forkIO $ do
328+ theirAddr <- readMVar processAddr
329+ runProcess localNode $ do
330+ monitor theirAddr
331+ -- wait for the process to die
332+ expect :: Process ProcessMonitorNotification
333+ monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
334+
335+ takeMVar done
336+
337+-- | Monitor a remote process that is already dead
338+testMonitorRemoteDeadProcess :: TestTransport -> Bool -> Bool -> Assertion
339+testMonitorRemoteDeadProcess TestTransport{..} mOrL un = do
340+ processDead <- newEmptyMVar
341+ processAddr <- newEmptyMVar
342+ done <- newEmptyMVar
343+
344+ forkIO $ do
345+ localNode <- newLocalNode testTransport initRemoteTable
346+ addr <- forkProcess localNode . liftIO $ putMVar processDead ()
347+ putMVar processAddr addr
348+
349+ forkIO $ do
350+ localNode <- newLocalNode testTransport initRemoteTable
351+ theirAddr <- readMVar processAddr
352+ readMVar processDead
353+ runProcess localNode $ do
354+ monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
355+
356+ takeMVar done
357+
358+-- | Monitor a process that becomes disconnected
359+testMonitorDisconnect :: TestTransport -> Bool -> Bool -> Assertion
360+testMonitorDisconnect TestTransport{..} mOrL un = do
361+ processAddr <- newEmptyMVar
362+ processAddr2 <- newEmptyMVar
363+ monitorSetup <- newEmptyMVar
364+ done <- newEmptyMVar
365+
366+ forkIO $ do
367+ localNode <- newLocalNode testTransport initRemoteTable
368+ addr <- forkProcess localNode $ expect
369+ addr2 <- forkProcess localNode $ return ()
370+ putMVar processAddr addr
371+ readMVar monitorSetup
372+ NT.closeEndPoint (localEndPoint localNode)
373+ putMVar processAddr2 addr2
374+
375+ forkIO $ do
376+ localNode <- newLocalNode testTransport initRemoteTable
377+ theirAddr <- readMVar processAddr
378+ forkProcess localNode $ do
379+ lc <- liftIO $ readMVar processAddr2
380+ send lc ()
381+ runProcess localNode $ do
382+ monitorTestProcess theirAddr mOrL un DiedDisconnect (Just monitorSetup) done
383+
384+ takeMVar done
385+
386+-- | Test the math server (i.e., receiveWait)
387+testMath :: TestTransport -> Assertion
388+testMath TestTransport{..} = do
389+ serverAddr <- newEmptyMVar
390+ clientDone <- newEmptyMVar
391+
392+ -- Server
393+ forkIO $ do
394+ localNode <- newLocalNode testTransport initRemoteTable
395+ addr <- forkProcess localNode math
396+ putMVar serverAddr addr
397+
398+ -- Client
399+ forkIO $ do
400+ localNode <- newLocalNode testTransport initRemoteTable
401+ mathServer <- readMVar serverAddr
402+
403+ runProcess localNode $ do
404+ pid <- getSelfPid
405+ send mathServer (Add pid 1 2)
406+ three <- expect :: Process Double
407+ send mathServer (Divide pid 8 2)
408+ four <- expect :: Process Double
409+ send mathServer (Divide pid 8 0)
410+ divByZ <- expect
411+ liftIO $ putMVar clientDone (three, four, divByZ)
412+
413+ res <- takeMVar clientDone
414+ case res of
415+ (3, 4, DivByZero) -> return ()
416+ _ -> error $ "Something went horribly wrong"
417+
418+-- | Send first message (i.e. connect) to an already terminated process
419+-- (without monitoring); then send another message to a second process on
420+-- the same remote node (we're checking that the remote node did not die)
421+testSendToTerminated :: TestTransport -> Assertion
422+testSendToTerminated TestTransport{..} = do
423+ serverAddr1 <- newEmptyMVar
424+ serverAddr2 <- newEmptyMVar
425+ clientDone <- newEmptyMVar
426+
427+ forkIO $ do
428+ terminated <- newEmptyMVar
429+ localNode <- newLocalNode testTransport initRemoteTable
430+ addr1 <- forkProcess localNode $ liftIO $ putMVar terminated ()
431+ addr2 <- forkProcess localNode $ ping
432+ readMVar terminated
433+ putMVar serverAddr1 addr1
434+ putMVar serverAddr2 addr2
435+
436+ forkIO $ do
437+ localNode <- newLocalNode testTransport initRemoteTable
438+ server1 <- readMVar serverAddr1
439+ server2 <- readMVar serverAddr2
440+ runProcess localNode $ do
441+ pid <- getSelfPid
442+ send server1 "Hi"
443+ send server2 (Pong pid)
444+ expectPing clientDone
445+
446+ verifyClient "Expected Ping from server" clientDone
447+
448+-- | Test (non-zero) timeout
449+testTimeout :: TestTransport -> Assertion
450+testTimeout TestTransport{..} = do
451+ localNode <- newLocalNode testTransport initRemoteTable
452+ done <- newEmptyMVar
453+
454+ runProcess localNode $ do
455+ res <- receiveTimeout 1000000 [match (\Add{} -> return ())]
456+ liftIO $ putMVar done $ res == Nothing
457+
458+ verifyClient "Expected receiveTimeout to timeout..." done
459+
460+-- | Test zero timeout
461+testTimeout0 :: TestTransport -> Assertion
462+testTimeout0 TestTransport{..} = do
463+ serverAddr <- newEmptyMVar
464+ clientDone <- newEmptyMVar
465+
466+ forkIO $ do
467+ localNode <- newLocalNode testTransport initRemoteTable
468+ addr <- forkProcess localNode $ do
469+ -- Variation on the venerable ping server which uses a zero timeout
470+ partner <- fix $ \loop ->
471+ receiveTimeout 0 [match (\(Pong partner) -> return partner)]
472+ >>= maybe (liftIO (threadDelay 100000) >> loop) return
473+ self <- getSelfPid
474+ send partner (Ping self)
475+ putMVar serverAddr addr
476+
477+ forkIO $ do
478+ localNode <- newLocalNode testTransport initRemoteTable
479+ server <- readMVar serverAddr
480+ runProcess localNode $ do
481+ pid <- getSelfPid
482+ -- Send a bunch of messages. A large number of messages that the server
483+ -- is not interested in, and then a single message that it wants
484+ replicateM_ 10000 $ send server "Irrelevant message"
485+ send server (Pong pid)
486+ expectPing clientDone
487+
488+ verifyClient "Expected Ping from server" clientDone
489+
490+-- | Test typed channels
491+testTypedChannels :: TestTransport -> Assertion
492+testTypedChannels TestTransport{..} = do
493+ serverChannel <- newEmptyMVar :: IO (MVar (SendPort (SendPort Bool, Int)))
494+ clientDone <- newEmptyMVar
495+
496+ forkIO $ do
497+ localNode <- newLocalNode testTransport initRemoteTable
498+ forkProcess localNode $ do
499+ (serverSendPort, rport) <- newChan
500+ liftIO $ putMVar serverChannel serverSendPort
501+ (clientSendPort, i) <- receiveChan rport
502+ sendChan clientSendPort (even i)
503+ return ()
504+
505+ forkIO $ do
506+ localNode <- newLocalNode testTransport initRemoteTable
507+ serverSendPort <- readMVar serverChannel
508+ runProcess localNode $ do
509+ (clientSendPort, rport) <- newChan
510+ sendChan serverSendPort (clientSendPort, 5)
511+ ch <- receiveChan rport
512+ liftIO $ putMVar clientDone $ ch == False
513+
514+ verifyClient "Expected channel to send 'False'" clientDone
515+
516+-- | Test merging receive ports
517+testMergeChannels :: TestTransport -> Assertion
518+testMergeChannels TestTransport{..} = do
519+ localNode <- newLocalNode testTransport initRemoteTable
520+ testFlat localNode True "aaabbbccc"
521+ testFlat localNode False "abcabcabc"
522+ testNested localNode True True "aaabbbcccdddeeefffggghhhiii"
523+ testNested localNode True False "adgadgadgbehbehbehcficficfi"
524+ testNested localNode False True "abcabcabcdefdefdefghighighi"
525+ testNested localNode False False "adgbehcfiadgbehcfiadgbehcfi"
526+ testBlocked localNode True
527+ testBlocked localNode False
528+ where
529+ -- Single layer of merging
530+ testFlat :: LocalNode -> Bool -> String -> IO ()
531+ testFlat localNode biased expected = do
532+ done <- newEmptyMVar
533+ forkProcess localNode $ do
534+ rs <- mapM charChannel "abc"
535+ m <- mergePorts biased rs
536+ xs <- replicateM 9 $ receiveChan m
537+ liftIO $ putMVar done $ xs == expected
538+ verifyClient "Expected single layer merge to match expected ordering" done
539+
540+ -- Two layers of merging
541+ testNested :: LocalNode -> Bool -> Bool -> String -> IO ()
542+ testNested localNode biasedInner biasedOuter expected = do
543+ done <- newEmptyMVar
544+ forkProcess localNode $ do
545+ rss <- mapM (mapM charChannel) ["abc", "def", "ghi"]
546+ ms <- mapM (mergePorts biasedInner) rss
547+ m <- mergePorts biasedOuter ms
548+ xs <- replicateM (9 * 3) $ receiveChan m
549+ liftIO $ putMVar done $ xs == expected
550+ verifyClient "Expected nested channels to match expeted ordering" done
551+
552+ -- Test that if no messages are (immediately) available, the scheduler makes no difference
553+ testBlocked :: LocalNode -> Bool -> IO ()
554+ testBlocked localNode biased = do
555+ vs <- replicateM 3 newEmptyMVar
556+ done <- newEmptyMVar
557+
558+ forkProcess localNode $ do
559+ ss <- liftIO $ mapM readMVar vs
560+ case ss of
561+ [sa, sb, sc] ->
562+ mapM_ ((>> pause 10000) . uncurry sendChan)
563+ [ -- a, b, c
564+ (sa, 'a')
565+ , (sb, 'b')
566+ , (sc, 'c')
567+ -- a, c, b
568+ , (sa, 'a')
569+ , (sc, 'c')
570+ , (sb, 'b')
571+ -- b, a, c
572+ , (sb, 'b')
573+ , (sa, 'a')
574+ , (sc, 'c')
575+ -- b, c, a
576+ , (sb, 'b')
577+ , (sc, 'c')
578+ , (sa, 'a')
579+ -- c, a, b
580+ , (sc, 'c')
581+ , (sa, 'a')
582+ , (sb, 'b')
583+ -- c, b, a
584+ , (sc, 'c')
585+ , (sb, 'b')
586+ , (sa, 'a')
587+ ]
588+ _ -> die "Something went horribly wrong"
589+
590+ forkProcess localNode $ do
591+ (ss, rs) <- unzip <$> replicateM 3 newChan
592+ liftIO $ mapM_ (uncurry putMVar) $ zip vs ss
593+ m <- mergePorts biased rs
594+ xs <- replicateM (6 * 3) $ receiveChan m
595+ liftIO $ putMVar done $ xs == "abcacbbacbcacabcba"
596+
597+ verifyClient "Expected merged ports to match expected ordering" done
598+
599+ mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)
600+ mergePorts True = mergePortsBiased
601+ mergePorts False = mergePortsRR
602+
603+ charChannel :: Char -> Process (ReceivePort Char)
604+ charChannel c = do
605+ (sport, rport) <- newChan
606+ replicateM_ 3 $ sendChan sport c
607+ liftIO $ threadDelay 10000 -- Make sure messages have been sent
608+ return rport
609+
610+testTerminate :: TestTransport -> Assertion
611+testTerminate TestTransport{..} = do
612+ localNode <- newLocalNode testTransport initRemoteTable
613+ runProcess localNode $ do
614+ e <- try terminate :: Process (Either ProcessTerminationException ())
615+ if either show show e == show ProcessTerminationException
616+ then return ()
617+ else die "Unexpected result from terminate"
618+
619+testMonitorNode :: TestTransport -> Assertion
620+testMonitorNode TestTransport{..} = do
621+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
622+ done <- newEmptyMVar
623+
624+ closeLocalNode node1
625+
626+ runProcess node2 $ do
627+ ref <- monitorNode (localNodeId node1)
628+ receiveWait [
629+ match (\(NodeMonitorNotification ref' nid DiedDisconnect) ->
630+ return $ ref == ref' && nid == localNodeId node1)
631+ ] >>= liftIO . putMVar done
632+
633+ verifyClient "Expected NodeMonitorNotification with matching ref & nodeId" done
634+
635+testMonitorLiveNode :: TestTransport -> Assertion
636+testMonitorLiveNode TestTransport{..} = do
637+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
638+ ready <- newEmptyMVar
639+ readyr <- newEmptyMVar
640+ done <- newEmptyMVar
641+
642+ p <- forkProcess node1 $ return ()
643+ forkProcess node2 $ do
644+ ref <- monitorNode (localNodeId node1)
645+ liftIO $ putMVar ready ()
646+ liftIO $ takeMVar readyr
647+ send p ()
648+ receiveWait [
649+ match (\(NodeMonitorNotification ref' nid _) ->
650+ (return $ ref == ref' && nid == localNodeId node1))
651+ ] >>= liftIO . putMVar done
652+
653+ takeMVar ready
654+ closeLocalNode node1
655+ putMVar readyr ()
656+
657+ verifyClient "Expected NodeMonitorNotification for LIVE node" done
658+
659+testMonitorChannel :: TestTransport -> Assertion
660+testMonitorChannel TestTransport{..} = do
661+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
662+ gotNotification <- newEmptyMVar
663+
664+ pid <- forkProcess node1 $ do
665+ sport <- expect :: Process (SendPort ())
666+ ref <- monitorPort sport
667+ receiveWait [
668+ -- reason might be DiedUnknownId if the receive port is GCed before the
669+ -- monitor is established (TODO: not sure that this is reasonable)
670+ match (\(PortMonitorNotification ref' port' reason) ->
671+ return $ ref' == ref && port' == sendPortId sport &&
672+ (reason == DiedNormal || reason == DiedUnknownId))
673+ ] >>= liftIO . putMVar gotNotification
674+
675+ runProcess node2 $ do
676+ (sport, _) <- newChan :: Process (SendPort (), ReceivePort ())
677+ send pid sport
678+ liftIO $ threadDelay 100000
679+
680+ verifyClient "Expected PortMonitorNotification" gotNotification
681+
682+testRegistry :: TestTransport -> Assertion
683+testRegistry TestTransport{..} = do
684+ node <- newLocalNode testTransport initRemoteTable
685+ done <- newEmptyMVar
686+
687+ pingServer <- forkProcess node ping
688+ deadProcess <- forkProcess node (return ())
689+
690+ runProcess node $ do
691+ register "ping" pingServer
692+ whereis "ping" >>= liftIO . assertBool "Unexpected ping" . (== Just pingServer)
693+ us <- getSelfPid
694+ nsend "ping" (Pong us)
695+ receiveWait [
696+ matchIf (\(Ping pid') -> pingServer == pid') (const $ return ())
697+ ]
698+ checkRegException "dead" Nothing deadProcess
699+ checkRegException "ping" (Just pingServer) deadProcess
700+ try (unregister "dead") >>= checkReg "dead" Nothing
701+ liftIO $ putMVar done ()
702+
703+ takeMVar done
704+
705+ where
706+ checkRegException name pid dead =
707+ try (register name dead) >>= checkReg name pid
708+
709+ checkReg name pid res =
710+ case res of
711+ Left (ProcessRegistrationException name pid) -> return ()
712+ _ -> die $ "Unexpected Registration" ++ show res
713+
714+testRegistryRemoteProcess :: TestTransport -> Assertion
715+testRegistryRemoteProcess TestTransport{..} = do
716+ node1 <- newLocalNode testTransport initRemoteTable
717+ node2 <- newLocalNode testTransport initRemoteTable
718+ done <- newEmptyMVar
719+
720+ pingServer <- forkProcess node1 ping
721+
722+ runProcess node2 $ do
723+ register "ping" pingServer
724+ whereis "ping" >>= liftIO . assertBool "Unexpected ping" . (== Just pingServer)
725+ us <- getSelfPid
726+ nsend "ping" (Pong us)
727+ receiveWait [
728+ matchIf (\(Ping pid') -> pingServer == pid')
729+ (const $ liftIO $ putMVar done ())
730+ ]
731+
732+ takeMVar done
733+
734+testRemoteRegistry :: TestTransport -> Assertion
735+testRemoteRegistry TestTransport{..} = do
736+ node1 <- newLocalNode testTransport initRemoteTable
737+ node2 <- newLocalNode testTransport initRemoteTable
738+
739+ pingServer <- forkProcess node1 ping
740+ deadProcess <- forkProcess node1 (return ())
741+
742+ runProcess node2 $ do
743+ let nid1 = localNodeId node1
744+ registerRemoteAsync nid1 "ping" pingServer
745+ receiveWait [
746+ matchIf (\(RegisterReply label' _ (Just pid)) ->
747+ "ping" == label' && pid == pingServer)
748+ (\(RegisterReply _ _ _) -> return ()) ]
749+
750+ pid <- verifyWhereIsRemote nid1 "ping"
751+ liftIO $ assertBool "Expected pindServer to match pid" $ pingServer == pid
752+
753+ us <- getSelfPid
754+ nsendRemote nid1 "ping" (Pong us)
755+ receiveWait [
756+ match (\(Ping pid') -> return $ pingServer == pid')
757+ ] >>= liftIO . assertBool "Expected Ping with ping server's ProcessId"
758+
759+ -- test that if process was not registered Nothing is returned
760+ -- in owner field.
761+ registerRemoteAsync nid1 "dead" deadProcess
762+ receiveWait [ matchIf (\(RegisterReply label' f mPid) -> "dead" == label')
763+ (\(RegisterReply _ f mPid) -> return (not f && isNothing mPid))
764+ ] >>= liftIO . assertBool "Expected False Nothing in RegisterReply"
765+
766+ registerRemoteAsync nid1 "ping" deadProcess
767+ receiveWait [
768+ matchIf (\(RegisterReply label' False mPid) ->
769+ "ping" == label' && isJust mPid)
770+ (\(RegisterReply _ f (Just pid'')) -> return (not f && pid'' == pingServer))
771+ ] >>= liftIO . assertBool "Expected False and (Just alreadyRegisteredPid) in RegisterReply"
772+
773+ unregisterRemoteAsync nid1 "dead"
774+ receiveWait [
775+ matchIf (\(RegisterReply label' _ _) -> "dead" == label')
776+ (\(RegisterReply _ f mPid) -> return (not f && isNothing mPid))
777+ ] >>= liftIO . assertBool "Expected False and Nothing in RegisterReply"
778+
779+testRemoteRegistryRemoteProcess :: TestTransport -> Assertion
780+testRemoteRegistryRemoteProcess TestTransport{..} = do
781+ node1 <- newLocalNode testTransport initRemoteTable
782+ node2 <- newLocalNode testTransport initRemoteTable
783+ done <- newEmptyMVar
784+
785+ pingServer <- forkProcess node2 ping
786+
787+ runProcess node2 $ do
788+ let nid1 = localNodeId node1
789+ registerRemoteAsync nid1 "ping" pingServer
790+ receiveWait [
791+ matchIf (\(RegisterReply label' _ _) -> "ping" == label')
792+ (\(RegisterReply _ _ _) -> return ()) ]
793+ pid <- verifyWhereIsRemote nid1 "ping"
794+ liftIO $ assertBool "Expected pingServer to match remote name" $ pingServer == pid
795+ us <- getSelfPid
796+ nsendRemote nid1 "ping" (Pong us)
797+ receiveWait [
798+ match (\(Ping pid') -> return $ pingServer == pid')
799+ ] >>= liftIO . putMVar done
800+
801+ verifyClient "Expected Ping with ping server's ProcessId" done
802+
803+testSpawnLocal :: TestTransport -> Assertion
804+testSpawnLocal TestTransport{..} = do
805+ node <- newLocalNode testTransport initRemoteTable
806+ done <- newEmptyMVar
807+
808+ runProcess node $ do
809+ us <- getSelfPid
810+
811+ pid <- spawnLocal $ do
812+ sport <- expect
813+ sendChan sport (1234 :: Int)
814+
815+ sport <- spawnChannelLocal $
816+ \rport -> (receiveChan rport :: Process Int) >>= send us
817+
818+ send pid sport
819+ expect >>= liftIO . putMVar done
820+
821+ res <- takeMVar done
822+ assertBool "Expected 1234 :: Int" $ res == (1234 :: Int)
823+
824+testSpawnAsyncStrictness :: TestTransport -> Assertion
825+testSpawnAsyncStrictness TestTransport{..} = do
826+ node <- newLocalNode testTransport initRemoteTable
827+ done <- newEmptyMVar
828+
829+ runProcess node $ do
830+ here <-getSelfNode
831+
832+ ev <- try $ spawnAsync here (error "boom")
833+ liftIO $ case ev of
834+ Right _ -> putMVar done (error "Exception didn't fire")
835+ Left (_::SomeException) -> putMVar done (return ())
836+
837+ join $ takeMVar done
838+
839+testReconnect :: TestTransport -> Assertion
840+testReconnect TestTransport{..} = do
841+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
842+ let nid1 = localNodeId node1
843+ processA <- newEmptyMVar
844+ [sendTestOk, registerTestOk] <- replicateM 2 newEmptyMVar
845+
846+ forkProcess node1 $ do
847+ us <- getSelfPid
848+ liftIO $ putMVar processA us
849+ msg1 <- expect
850+ msg2 <- expect
851+ liftIO $ do
852+ assertBool "messages did not match" $ msg1 == "message 1" && msg2 == "message 3"
853+ putMVar sendTestOk ()
854+
855+ forkProcess node2 $ do
856+ {-
857+ - Make sure there is no implicit reconnect on normal message sending
858+ -}
859+
860+ them <- liftIO $ readMVar processA
861+ send them "message 1" >> liftIO (threadDelay 100000)
862+
863+ -- Simulate network failure
864+ liftIO $ syncBreakConnection testBreakConnection node1 node2
865+
866+
867+ -- Should not arrive
868+ send them "message 2"
869+
870+ -- Should arrive
871+ reconnect them
872+ send them "message 3"
873+
874+ liftIO $ takeMVar sendTestOk
875+
876+ {-
877+ - Test that there *is* implicit reconnect on node controller messages
878+ -}
879+
880+ us <- getSelfPid
881+ registerRemoteAsync nid1 "a" us -- registerRemote is asynchronous
882+ receiveWait [
883+ matchIf (\(RegisterReply label' _ _) -> "a" == label')
884+ (\(RegisterReply _ _ _) -> return ()) ]
885+
886+ _ <- whereisRemote nid1 "a"
887+
888+
889+ -- Simulate network failure
890+ liftIO $ syncBreakConnection testBreakConnection node1 node2
891+
892+ -- This will happen due to implicit reconnect
893+ registerRemoteAsync nid1 "b" us
894+ receiveWait [
895+ matchIf (\(RegisterReply label' _ _) -> "b" == label')
896+ (\(RegisterReply _ _ _) -> return ()) ]
897+
898+ -- Should happen
899+ registerRemoteAsync nid1 "c" us
900+ receiveWait [
901+ matchIf (\(RegisterReply label' _ _) -> "c" == label')
902+ (\(RegisterReply _ _ _) -> return ()) ]
903+
904+ -- Check
905+ mPid <- whereisRemote nid1 "a" -- this will fail because the name is removed when the node is disconnected
906+ liftIO $ assertBool "Expected remote name to be lost" $ mPid == Nothing
907+ verifyWhereIsRemote nid1 "b" -- this will suceed because the value is set after thereconnect
908+ verifyWhereIsRemote nid1 "c"
909+
910+ liftIO $ putMVar registerTestOk ()
911+
912+ takeMVar registerTestOk
913+
914+-- | Tests that unreliable messages arrive sorted even when there are connection
915+-- failures.
916+testUSend :: (ProcessId -> Int -> Process ())
917+ -> TestTransport -> Int -> Assertion
918+testUSend usendPrim TestTransport{..} numMessages = do
919+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
920+ let nid1 = localNodeId node1
921+ nid2 = localNodeId node2
922+ processA <- newEmptyMVar
923+ usendTestOk <- newEmptyMVar
924+
925+ forkProcess node1 $ flip catch (\e -> liftIO $ print (e :: SomeException) ) $ do
926+ us <- getSelfPid
927+ liftIO $ putMVar processA us
928+ them <- expect
929+ send them ()
930+ _ <- monitor them
931+ let -- Collects messages from 'them' until the sender dies.
932+ -- Disconnection notifications are ignored.
933+ receiveMessages :: Process [Int]
934+ receiveMessages = receiveWait
935+ [ match $ \mn -> case mn of
936+ ProcessMonitorNotification _ _ DiedDisconnect -> do
937+ monitor them
938+ receiveMessages
939+ _ -> return []
940+ , match $ \i -> fmap (i :) receiveMessages
941+ ]
942+ msgs <- receiveMessages
943+ let -- Checks that the input list is sorted.
944+ isSorted :: [Int] -> Bool
945+ isSorted (x : xs@(y : _)) = x <= y && isSorted xs
946+ isSorted _ = True
947+ -- The list can't be null since there are no failures after sending
948+ -- the last message.
949+ liftIO $ putMVar usendTestOk $ isSorted msgs && not (null msgs)
950+
951+ forkProcess node2 $ do
952+ them <- liftIO $ readMVar processA
953+ getSelfPid >>= send them
954+ expect :: Process ()
955+ forM_ [1..numMessages] $ \i -> do
956+ liftIO $ testBreakConnection (nodeAddress nid1) (nodeAddress nid2)
957+ usendPrim them i
958+ liftIO (threadDelay 30000)
959+
960+ res <- takeMVar usendTestOk
961+ assertBool "Unexpected failure after sending last msg" res
962+
963+-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
964+-- in between
965+testMatchAny :: TestTransport -> Assertion
966+testMatchAny TestTransport{..} = do
967+ proxyAddr <- newEmptyMVar
968+ clientDone <- newEmptyMVar
969+
970+ -- Math server
971+ forkIO $ do
972+ localNode <- newLocalNode testTransport initRemoteTable
973+ mathServer <- forkProcess localNode math
974+ proxyServer <- forkProcess localNode $ forever $ do
975+ msg <- receiveWait [ matchAny return ]
976+ forward msg mathServer
977+ putMVar proxyAddr proxyServer
978+
979+ -- Client
980+ forkIO $ do
981+ localNode <- newLocalNode testTransport initRemoteTable
982+ mathServer <- readMVar proxyAddr
983+
984+ runProcess localNode $ do
985+ pid <- getSelfPid
986+ send mathServer (Add pid 1 2)
987+ three <- expect :: Process Double
988+ send mathServer (Divide pid 8 2)
989+ four <- expect :: Process Double
990+ send mathServer (Divide pid 8 0)
991+ divByZ <- expect
992+ liftIO $ putMVar clientDone (three, four, divByZ)
993+
994+ res <- takeMVar clientDone
995+ case res of
996+ (3, 4, DivByZero) -> return ()
997+ _ -> error "Unexpected result"
998+
999+-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
1000+-- in between, however we block 'Divide' requests ....
1001+testMatchAnyHandle :: TestTransport -> Assertion
1002+testMatchAnyHandle TestTransport{..} = do
1003+ proxyAddr <- newEmptyMVar
1004+ clientDone <- newEmptyMVar
1005+
1006+ -- Math server
1007+ forkIO $ do
1008+ localNode <- newLocalNode testTransport initRemoteTable
1009+ mathServer <- forkProcess localNode math
1010+ proxyServer <- forkProcess localNode $ forever $ do
1011+ receiveWait [
1012+ matchAny (maybeForward mathServer)
1013+ ]
1014+ putMVar proxyAddr proxyServer
1015+
1016+ -- Client
1017+ forkIO $ do
1018+ localNode <- newLocalNode testTransport initRemoteTable
1019+ mathServer <- readMVar proxyAddr
1020+
1021+ runProcess localNode $ do
1022+ pid <- getSelfPid
1023+ send mathServer (Add pid 1 2)
1024+ three <- expect :: Process Double
1025+ send mathServer (Divide pid 8 2)
1026+ res <- (expectTimeout 100000) :: Process (Maybe Double)
1027+ liftIO $ putMVar clientDone $ three == 3 && res == Nothing
1028+
1029+ verifyClient "Expected Nothing (i.e. timeout)" clientDone
1030+
1031+ where maybeForward :: ProcessId -> Message -> Process (Maybe ())
1032+ maybeForward s msg =
1033+ handleMessage msg (\m@(Add _ _ _) -> send s m)
1034+
1035+testMatchAnyNoHandle :: TestTransport -> Assertion
1036+testMatchAnyNoHandle TestTransport{..} = do
1037+ addr <- newEmptyMVar
1038+ clientDone <- newEmptyMVar
1039+ serverDone <- newEmptyMVar
1040+
1041+ -- Math server
1042+ forkIO $ do
1043+ localNode <- newLocalNode testTransport initRemoteTable
1044+ server <- forkProcess localNode $ forever $ do
1045+ receiveWait [
1046+ matchAnyIf
1047+ -- the condition has type `Add -> Bool`
1048+ (\(Add _ _ _) -> True)
1049+ -- the match `AbstractMessage -> Process ()` will succeed!
1050+ (\m -> do
1051+ -- `String -> Process ()` does *not* match the input types however
1052+ r <- (handleMessage m (\(_ :: String) -> die "NONSENSE" ))
1053+ case r of
1054+ Nothing -> return ()
1055+ Just _ -> die "NONSENSE")
1056+ ]
1057+ -- we *must* have removed the message from our mailbox though!!!
1058+ res <- receiveTimeout 100000 [ match (\(Add _ _ _) -> return ()) ]
1059+ liftIO $ do
1060+ assertBool "Expected timeout!" $ res == Nothing
1061+ putMVar serverDone ()
1062+ putMVar addr server
1063+
1064+ -- Client
1065+ forkIO $ do
1066+ localNode <- newLocalNode testTransport initRemoteTable
1067+ server <- readMVar addr
1068+
1069+ runProcess localNode $ do
1070+ pid <- getSelfPid
1071+ send server (Add pid 1 2)
1072+ -- we only care about the client having sent a message, so we're done
1073+ liftIO $ putMVar clientDone ()
1074+
1075+ takeMVar clientDone
1076+ takeMVar serverDone
1077+
1078+-- | Test 'matchAnyIf'. We provide an /echo/ server, but it ignores requests
1079+-- unless the text body @/= "bar"@ - this case should time out rather than
1080+-- removing the message from the process mailbox.
1081+testMatchAnyIf :: TestTransport -> Assertion
1082+testMatchAnyIf TestTransport{..} = do
1083+ echoAddr <- newEmptyMVar
1084+ clientDone <- newEmptyMVar
1085+
1086+ -- echo server
1087+ forkIO $ do
1088+ localNode <- newLocalNode testTransport initRemoteTable
1089+ echoServer <- forkProcess localNode $ forever $ do
1090+ receiveWait [
1091+ matchAnyIf (\(_ :: ProcessId, (s :: String)) -> s /= "bar")
1092+ tryHandleMessage
1093+ ]
1094+ putMVar echoAddr echoServer
1095+
1096+ -- Client
1097+ forkIO $ do
1098+ localNode <- newLocalNode testTransport initRemoteTable
1099+ server <- readMVar echoAddr
1100+
1101+ runProcess localNode $ do
1102+ pid <- getSelfPid
1103+ send server (pid, "foo")
1104+ foo <- expect
1105+ -- provoking what would be the wrong ordering is informative here...
1106+
1107+ send server (pid, "bar")
1108+ bar <- (expectTimeout 100000) :: Process (Maybe String) -- was Double o_O !?
1109+
1110+ send server (pid, "baz")
1111+ baz <- expect
1112+
1113+ liftIO $ putMVar clientDone (foo, bar, baz)
1114+
1115+ res <- takeMVar clientDone
1116+ let res' = res == ("foo", Nothing, "baz")
1117+ assertBool "Expected timeout due to type mismatch" res'
1118+
1119+ where tryHandleMessage :: Message -> Process (Maybe ())
1120+ tryHandleMessage msg =
1121+ handleMessage msg (\(pid :: ProcessId, (m :: String))
1122+ -> do { send pid m; return () })
1123+
1124+testMatchMessageWithUnwrap :: TestTransport -> Assertion
1125+testMatchMessageWithUnwrap TestTransport{..} = do
1126+ echoAddr <- newEmptyMVar
1127+ clientDone <- newEmptyMVar
1128+
1129+ -- echo server
1130+ forkIO $ do
1131+ localNode <- newLocalNode testTransport initRemoteTable
1132+ echoServer <- forkProcess localNode $ forever $ do
1133+ msg <- receiveWait [
1134+ matchMessage (\(m :: Message) -> do
1135+ return m)
1136+ ]
1137+ unwrapped <- unwrapMessage msg :: Process (Maybe (ProcessId, Message))
1138+ case unwrapped of
1139+ (Just (p, msg')) -> forward msg' p
1140+ Nothing -> die "unable to unwrap the message"
1141+ putMVar echoAddr echoServer
1142+
1143+ -- Client
1144+ forkIO $ do
1145+ localNode <- newLocalNode testTransport initRemoteTable
1146+ server <- readMVar echoAddr
1147+
1148+ runProcess localNode $ do
1149+ pid <- getSelfPid
1150+ send server (pid, wrapMessage ("foo" :: String))
1151+ foo <- expect
1152+ send server (pid, wrapMessage ("baz" :: String))
1153+ baz <- expect
1154+ liftIO $ putMVar clientDone (foo, baz)
1155+
1156+ res <- takeMVar clientDone
1157+ assertBool "Unexpected unwrapped results" $ res == ("foo", "baz")
1158+
1159+-- Test 'receiveChanTimeout'
1160+testReceiveChanTimeout :: TestTransport -> Assertion
1161+testReceiveChanTimeout TestTransport{..} = do
1162+ done <- newEmptyMVar
1163+ mvSender <- newEmptyMVar
1164+ sendPort <- newEmptyMVar
1165+
1166+ forkTry $ do
1167+ localNode <- newLocalNode testTransport initRemoteTable
1168+ runProcess localNode $ do
1169+ -- Create a typed channel
1170+ (sp, rp) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
1171+ liftIO $ putMVar sendPort sp
1172+
1173+ -- Wait for a message with a delay. No message arrives, we should get
1174+ -- Nothing after the delay.
1175+ receiveChanTimeout 100000 rp >>= maybe (return ()) (const $ die "Expected Timeout")
1176+
1177+ -- Let the sender know that it can send a message.
1178+ liftIO $ putMVar mvSender ()
1179+
1180+ -- Wait for a message with a delay again. Now a message arrives after
1181+ -- 0.1 seconds
1182+ res <- receiveChanTimeout 20000000 rp >>= maybe (die "Timeout") return
1183+ liftIO $ assertBool "Expected result to be 'True'" res
1184+
1185+ -- Wait for a message with zero timeout: non-blocking check. No message is
1186+ -- available, we get Nothing
1187+ receiveChanTimeout 0 rp >>= maybe (return ()) (const $ die "Expected Timeout")
1188+
1189+ -- Let the sender know that it can send a message.
1190+ liftIO $ putMVar mvSender ()
1191+
1192+ -- Again, but now there is a message available
1193+ fix $ \loop -> do
1194+ liftIO $ threadDelay 100000
1195+ mb <- receiveChanTimeout 0 rp
1196+ case mb of
1197+ Just b -> do liftIO $ assertBool "Unexpected Message" $ not b
1198+ _ -> loop
1199+
1200+ liftIO $ putMVar done ()
1201+
1202+ forkTry $ do
1203+ localNode <- newLocalNode testTransport initRemoteTable
1204+ runProcess localNode $ do
1205+ sp <- liftIO $ readMVar sendPort
1206+
1207+ liftIO $ takeMVar mvSender
1208+ liftIO $ threadDelay 100000
1209+ sendChan sp True
1210+
1211+ liftIO $ takeMVar mvSender
1212+ sendChan sp False
1213+
1214+ takeMVar done
1215+
1216+-- | Test Functor, Applicative, Alternative and Monad instances for ReceiveChan
1217+testReceiveChanFeatures :: TestTransport -> Assertion
1218+testReceiveChanFeatures TestTransport{..} = do
1219+ done <- newEmptyMVar
1220+
1221+ forkTry $ do
1222+ localNode <- newLocalNode testTransport initRemoteTable
1223+ runProcess localNode $ do
1224+ (spInt, rpInt) <- newChan :: Process (SendPort Int, ReceivePort Int)
1225+ (spBool, rpBool) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
1226+
1227+ -- Test Functor instance
1228+
1229+ sendChan spInt 2
1230+ sendChan spBool False
1231+
1232+ rp1 <- mergePortsBiased [even <$> rpInt, rpBool]
1233+
1234+ receiveChan rp1 >>= liftIO . assertBool "Expected True"
1235+ receiveChan rp1 >>= liftIO . assertBool "Expected False" . not
1236+
1237+ -- Test Applicative instance
1238+
1239+ sendChan spInt 3
1240+ sendChan spInt 4
1241+
1242+ let rp2 = pure (+) <*> rpInt <*> rpInt
1243+
1244+ receiveChan rp2 >>= liftIO . assertBool "Expected 7" . (== 7)
1245+
1246+ -- Test Alternative instance
1247+
1248+ sendChan spInt 3
1249+ sendChan spBool True
1250+
1251+ let rp3 = (even <$> rpInt) <|> rpBool
1252+
1253+ receiveChan rp3 >>= liftIO . assertBool "Expected False" . not
1254+ receiveChan rp3 >>= liftIO . assertBool "Expected True"
1255+
1256+ -- Test Monad instance
1257+
1258+ sendChan spBool True
1259+ sendChan spBool False
1260+ sendChan spInt 5
1261+
1262+ let rp4 :: ReceivePort Int
1263+ rp4 = do b <- rpBool
1264+ if b
1265+ then rpInt
1266+ else return 7
1267+
1268+ receiveChan rp4 >>= liftIO . assertBool "Expected 5" . (== 5)
1269+ receiveChan rp4 >>= liftIO . assertBool "Expected 7" . (== 7)
1270+
1271+ liftIO $ putMVar done ()
1272+
1273+ takeMVar done
1274+
1275+testChanLifecycle :: TestTransport -> Assertion
1276+testChanLifecycle TestTransport{..} = let delay = 3000000 in do
1277+ result <- newEmptyMVar
1278+ tchMV <- newEmptyMVar
1279+ localNode <- newLocalNode testTransport initRemoteTable
1280+ runProcess localNode $ do
1281+
1282+ pid <- spawnLocal $ do tCh <- newChan :: Process (SendPort (), ReceivePort ())
1283+ liftIO $ putMVar tchMV tCh
1284+ expect :: Process ()
1285+ let (sp, _) = tCh
1286+ sendChan sp ()
1287+ expect :: Process ()
1288+
1289+ mRefPid <- monitor pid
1290+
1291+ cPid <- spawnLocal $ do
1292+ (sp', rp) <- liftIO $ takeMVar tchMV
1293+ -- say "picked up our test channel"
1294+ send pid ()
1295+ -- say "told pid to continue"
1296+ res <- receiveChanTimeout delay rp
1297+ case res of
1298+ Nothing -> say "initial chan () missing!" >> (liftIO $ putMVar result False)
1299+ Just () -> do mr <- monitor pid
1300+ pause 10000
1301+ -- say "sending pid a second () will cause it to exit"
1302+ send pid ()
1303+
1304+ -- say "make sure we see a DOWN notification for pid having stopped"
1305+ receiveWait [ match (\(_ :: ProcessMonitorNotification) -> return ()) ]
1306+
1307+ -- now that pid has died, the send port should be useless...
1308+ liftIO $ putMVar tchMV (sp', rp)
1309+
1310+ -- let's verify that we do not see the message from our
1311+ -- parent process on the channel, once pid has died...
1312+ recv <- receiveChanTimeout delay rp
1313+ -- say $ "finished waiting for second (), writing result" ++ (show recv)
1314+ liftIO $ putMVar result $ isNothing recv
1315+
1316+ mRefCPid <- monitor cPid
1317+
1318+ receiveWait
1319+ [ matchIf (\(ProcessMonitorNotification r _ _) -> r == mRefPid)
1320+ (\_ -> return ())
1321+ ]
1322+
1323+ -- say "seen first pid die..."
1324+
1325+ (sendPort, _) <- liftIO $ takeMVar tchMV
1326+ sendChan sendPort ()
1327+ -- say "sent () after owning pid died"
1328+
1329+ -- let cPid know we've written to the channel...
1330+ send cPid ()
1331+
1332+ receiveWait
1333+ [ matchIf (\(ProcessMonitorNotification r _ _) -> r == mRefCPid)
1334+ (\_ -> return ())
1335+ ]
1336+
1337+ -- say "seen both pids die now..."
1338+
1339+ -- and wait on the result back in IO land...
1340+ testRes <- takeMVar result
1341+ -- runProcess localNode $ say "got result..."
1342+ assertBool "Expected sending on the channel to fail, but received data!" testRes
1343+
1344+
1345+testKillLocal :: TestTransport -> Assertion
1346+testKillLocal TestTransport{..} = do
1347+ localNode <- newLocalNode testTransport initRemoteTable
1348+
1349+ pid <- forkProcess localNode $ do
1350+ liftIO $ threadDelay 1000000
1351+
1352+ runProcess localNode $ do
1353+ ref <- monitor pid
1354+ us <- getSelfPid
1355+ kill pid "TestKill"
1356+ mn <- expect
1357+ case mn of
1358+ ProcessMonitorNotification ref' pid' (DiedException ex) ->
1359+ case ref == ref' && pid == pid' && ex == "killed-by=" ++ show us ++ ",reason=TestKill" of
1360+ True -> return ()
1361+ False -> die "Invalid ProcessMonitorNotification received"
1362+ _ -> die "Something went horribly wrong"
1363+
1364+testKillRemote :: TestTransport -> Assertion
1365+testKillRemote TestTransport{..} = do
1366+ node1 <- newLocalNode testTransport initRemoteTable
1367+ node2 <- newLocalNode testTransport initRemoteTable
1368+
1369+ pid <- forkProcess node1 $ do
1370+ liftIO $ threadDelay 1000000
1371+
1372+ runProcess node2 $ do
1373+ ref <- monitor pid
1374+ us <- getSelfPid
1375+ kill pid "TestKill"
1376+ mn <- expect
1377+ case mn of
1378+ ProcessMonitorNotification ref' pid' (DiedException reason) ->
1379+ case (ref == ref', pid == pid', reason == "killed-by=" ++ show us ++ ",reason=TestKill") of
1380+ (True, True, True) -> return ()
1381+ (a, b, c) -> do
1382+ let a' = if a then "" else "Invalid ref"
1383+ let b' = if b then "" else "Invalid pid"
1384+ let c' = if c then "" else "Invalid message"
1385+ die $ unwords [a', b', c']
1386+ _ -> die "Received unexpected message"
1387+
1388+testCatchesExit :: TestTransport -> Assertion
1389+testCatchesExit TestTransport{..} = do
1390+ localNode <- newLocalNode testTransport initRemoteTable
1391+ done <- newEmptyMVar
1392+
1393+ _ <- forkProcess localNode $ do
1394+ (die ("foobar", 123 :: Int))
1395+ `catchesExit` [
1396+ (\_ m -> handleMessage m (\(_ :: String) -> return ()))
1397+ , (\_ m -> handleMessage m (\(_ :: Maybe Int) -> return ()))
1398+ , (\_ m -> handleMessage m (\(_ :: String, _ :: Int)
1399+ -> (liftIO $ putMVar done ()) >> return ()))
1400+ ]
1401+
1402+ takeMVar done
1403+
1404+testHandleMessageIf :: TestTransport -> Assertion
1405+testHandleMessageIf TestTransport{..} = do
1406+ localNode <- newLocalNode testTransport initRemoteTable
1407+ done <- newEmptyMVar
1408+ _ <- forkProcess localNode $ do
1409+ self <- getSelfPid
1410+ send self (5 :: Integer, 10 :: Integer)
1411+ msg <- receiveWait [ matchMessage return ]
1412+ handleMessageIf msg
1413+ (\() -> True)
1414+ (\() -> die "whoops") >>= maybe (return ())
1415+ (const $ die "Expected Mismatch")
1416+ handleMessageIf msg (\(x :: Integer, y :: Integer) -> x == 5 && y == 10)
1417+ (\input -> liftIO $ putMVar done input)
1418+ return ()
1419+
1420+ result <- takeMVar done
1421+ expectThat result $ equalTo (5, 10)
1422+
1423+testCatches :: TestTransport -> Assertion
1424+testCatches TestTransport{..} = do
1425+ localNode <- newLocalNode testTransport initRemoteTable
1426+ done <- newEmptyMVar
1427+
1428+ _ <- forkProcess localNode $ do
1429+ node <- getSelfNode
1430+ (liftIO $ throwIO (ProcessLinkException (nullProcessId node) DiedNormal))
1431+ `catches` [
1432+ Handler (\(ProcessLinkException _ _) -> liftIO $ putMVar done ())
1433+ ]
1434+
1435+ takeMVar done
1436+
1437+testMaskRestoreScope :: TestTransport -> Assertion
1438+testMaskRestoreScope TestTransport{..} = do
1439+ localNode <- newLocalNode testTransport initRemoteTable
1440+ parentPid <- newEmptyMVar :: IO (MVar ProcessId)
1441+ spawnedPid <- newEmptyMVar :: IO (MVar ProcessId)
1442+
1443+ void $ runProcess localNode $ mask $ \unmask -> do
1444+ getSelfPid >>= liftIO . putMVar parentPid
1445+ void $ spawnLocal $ unmask (getSelfPid >>= liftIO . putMVar spawnedPid)
1446+
1447+ parent <- liftIO $ takeMVar parentPid
1448+ child <- liftIO $ takeMVar spawnedPid
1449+ expectThat parent $ isNot $ equalTo child
1450+
1451+testDie :: TestTransport -> Assertion
1452+testDie TestTransport{..} = do
1453+ localNode <- newLocalNode testTransport initRemoteTable
1454+ done <- newEmptyMVar
1455+
1456+ _ <- forkProcess localNode $ do
1457+ (die ("foobar", 123 :: Int))
1458+ `catchExit` \_from reason -> do
1459+ -- TODO: should verify that 'from' has the right value
1460+ let res = reason == ("foobar", 123 :: Int)
1461+ liftIO $ putMVar done ()
1462+ if res
1463+ then return ()
1464+ else die "Something went horribly wrong"
1465+
1466+ takeMVar done
1467+
1468+testPrettyExit :: TestTransport -> Assertion
1469+testPrettyExit TestTransport{..} = do
1470+ localNode <- newLocalNode testTransport initRemoteTable
1471+ done <- newEmptyMVar
1472+
1473+ _ <- forkProcess localNode $ do
1474+ (die "timeout")
1475+ `catch` \ex@(ProcessExitException from _) ->
1476+ let expected = "exit-from=" ++ (show from)
1477+ in do
1478+ let res = (show ex) == expected
1479+ liftIO $ putMVar done ()
1480+ if res
1481+ then return ()
1482+ else die "Something went horribly wrong"
1483+
1484+ takeMVar done
1485+
1486+testExitLocal :: TestTransport -> Assertion
1487+testExitLocal TestTransport{..} = do
1488+ localNode <- newLocalNode testTransport initRemoteTable
1489+ supervisedDone <- newEmptyMVar
1490+ supervisorDone <- newEmptyMVar
1491+ -- XXX: we guarantee that exception handler will be set up
1492+ -- regardless if forkProcess preserve masking state or not.
1493+ handlerSetUp <- newEmptyMVar
1494+
1495+ pid <- forkProcess localNode $ do
1496+ (liftIO (putMVar handlerSetUp ()) >> expect) `catchExit` \_from reason -> do
1497+ -- TODO: should verify that 'from' has the right value
1498+ res <- return $ reason == "TestExit"
1499+ liftIO $ putMVar supervisedDone ()
1500+ if res
1501+ then return ()
1502+ else die "Something went horribly wrong"
1503+
1504+ runProcess localNode $ do
1505+ liftIO $ takeMVar handlerSetUp
1506+ ref <- monitor pid
1507+ exit pid "TestExit"
1508+ -- This time the client catches the exception, so it dies normally
1509+ mn <- expect
1510+ case mn of
1511+ ProcessMonitorNotification ref' pid' DiedNormal -> do
1512+ let res = ref == ref' && pid == pid'
1513+ liftIO $ putMVar supervisorDone ()
1514+ if res
1515+ then return ()
1516+ else die "Something went horribly wrong"
1517+ _ -> die "Something went horribly wrong"
1518+
1519+ takeMVar supervisedDone
1520+ takeMVar supervisorDone
1521+
1522+testExitRemote :: TestTransport -> Assertion
1523+testExitRemote TestTransport{..} = do
1524+ node1 <- newLocalNode testTransport initRemoteTable
1525+ node2 <- newLocalNode testTransport initRemoteTable
1526+ supervisedDone <- newEmptyMVar
1527+ supervisorDone <- newEmptyMVar
1528+
1529+ pid <- forkProcess node1 $ do
1530+ (receiveWait [] :: Process ()) -- block forever
1531+ `catchExit` \_from reason -> do
1532+ -- TODO: should verify that 'from' has the right value
1533+ res <- return $ reason == "TestExit"
1534+ liftIO $ putMVar supervisedDone ()
1535+ if res
1536+ then return ()
1537+ else die "Something went horribly wrong"
1538+
1539+ runProcess node2 $ do
1540+ ref <- monitor pid
1541+ exit pid "TestExit"
1542+ mn <- expect
1543+ case mn of
1544+ ProcessMonitorNotification ref' pid' DiedNormal -> do
1545+ res' <- return $ ref == ref' && pid == pid'
1546+ liftIO $ putMVar supervisorDone ()
1547+ if res'
1548+ then return ()
1549+ else die "Something went horribly wrong"
1550+ _ -> die "Something went horribly wrong"
1551+
1552+ takeMVar supervisedDone
1553+ takeMVar supervisorDone
1554+
1555+testRegistryMonitoring :: TestTransport -> Assertion
1556+testRegistryMonitoring TestTransport{..} = do
1557+ node1 <- newLocalNode testTransport initRemoteTable
1558+ node2 <- newLocalNode testTransport initRemoteTable
1559+
1560+ let nid = localNodeId node2
1561+ pid <- forkProcess node1 $ do
1562+ self <- getSelfPid
1563+ runUntilRegistered nid self
1564+ say $ (show self) ++ " registered as " ++ regName
1565+ expect :: Process ()
1566+ say $ (show self) ++ " exiting normally"
1567+
1568+ runProcess node2 $ do
1569+ register regName pid
1570+ say $ regName ++ " registered to " ++ show pid
1571+ res <- whereis regName
1572+ send pid ()
1573+ say $ " sent finish signal to " ++ show pid
1574+ us <- getSelfPid
1575+ liftIO $ assertBool "expected (Just pid)" $ res == (Just pid)
1576+
1577+
1578+ -- This delay isn't essential!
1579+ -- The test case passes perfectly fine without it (feel free to comment out
1580+ -- and see), however waiting a few seconds here, makes it much more likely
1581+ -- that in delayUntilMaybeUnregistered we will hit the match case right
1582+ -- away, and thus not be subjected to a 20 second delay. The value of 4
1583+ -- seconds appears to work optimally on osx and across several linux distros
1584+ -- running in virtual machines (which is essentially what we do in CI)
1585+ void $ receiveTimeout 4000000 [ matchAny return ]
1586+
1587+ -- This delay doesn't serve much purpose in the happy path, however if some
1588+ -- future patch breaks the cooperative behaviour of node controllers viz
1589+ -- remote process registration and notification taking place via ncEffectDied,
1590+ -- there would be the possibility of a race in the test case should we attempt
1591+ -- to evaluate `whereis regName` on node2 right away. In case the name is still
1592+ -- erroneously registered, observing the 20 second delay (or lack of), could at
1593+ -- least give a hint that something is wrong, and we give up our time slice
1594+ -- so that there's a higher change the registrations have been cleaned up
1595+ -- in either case.
1596+ runProcess node2 $ delayUntilMaybeUnregistered nid pid
1597+
1598+ regHere <- newEmptyMVar
1599+ runProcess node2 $ whereis regName >>= liftIO . putMVar regHere
1600+ res <- takeMVar regHere
1601+ case res of
1602+ Nothing -> return ()
1603+ Just pid -> assertBool ("expected Nothing, but got " ++ show pid) False
1604+
1605+ where
1606+ runUntilRegistered nid us = do
1607+ whereisRemoteAsync nid regName
1608+ receiveWait [
1609+ matchIf (\(WhereIsReply n (Just p)) -> n == regName && p == us)
1610+ (const $ return ())
1611+ ]
1612+
1613+ delayUntilMaybeUnregistered nid p = do
1614+ whereisRemoteAsync nid regName
1615+ res <- receiveTimeout 20000000 {- 20 sec delay -} [
1616+ matchIf (\(WhereIsReply n p) -> n == regName && isNothing p)
1617+ (const $ return ())
1618+ ]
1619+ case res of
1620+ Just () -> return ()
1621+ Nothing -> delayUntilMaybeUnregistered nid p
1622+
1623+ regName = "testRegisterRemote"
1624+
1625+testUnsafeSend :: TestTransport -> Assertion
1626+testUnsafeSend TestTransport{..} = do
1627+ serverAddr <- newEmptyMVar
1628+ clientDone <- newEmptyMVar
1629+
1630+ localNode <- newLocalNode testTransport initRemoteTable
1631+ void $ forkProcess localNode $ do
1632+ self <- getSelfPid
1633+ liftIO $ putMVar serverAddr self
1634+ clientAddr <- expect
1635+ unsafeSend clientAddr ()
1636+
1637+ void $ forkProcess localNode $ do
1638+ serverPid <- liftIO $ takeMVar serverAddr
1639+ getSelfPid >>= unsafeSend serverPid
1640+ expect >>= liftIO . putMVar clientDone
1641+
1642+ takeMVar clientDone
1643+
1644+testUnsafeUSend :: TestTransport -> Assertion
1645+testUnsafeUSend TestTransport{..} = do
1646+ serverAddr <- newEmptyMVar
1647+ clientDone <- newEmptyMVar
1648+
1649+ localNode <- newLocalNode testTransport initRemoteTable
1650+ void $ forkProcess localNode $ do
1651+ self <- getSelfPid
1652+ liftIO $ putMVar serverAddr self
1653+ clientAddr <- expect
1654+ unsafeUSend clientAddr ()
1655+
1656+ void $ forkProcess localNode $ do
1657+ serverPid <- liftIO $ takeMVar serverAddr
1658+ getSelfPid >>= unsafeUSend serverPid
1659+ expect >>= liftIO . putMVar clientDone
1660+
1661+ takeMVar clientDone
1662+
1663+testUnsafeNSend :: TestTransport -> Assertion
1664+testUnsafeNSend TestTransport{..} = do
1665+ clientDone <- newEmptyMVar
1666+
1667+ localNode <- newLocalNode testTransport initRemoteTable
1668+
1669+ pid <- forkProcess localNode $ do
1670+ expect >>= liftIO . putMVar clientDone
1671+
1672+ void $ runProcess localNode $ do
1673+ register "foobar" pid
1674+ unsafeNSend "foobar" ()
1675+
1676+ takeMVar clientDone
1677+
1678+testUnsafeNSendRemote :: TestTransport -> Assertion
1679+testUnsafeNSendRemote TestTransport{..} = do
1680+ clientDone <- newEmptyMVar
1681+
1682+ localNode1 <- newLocalNode testTransport initRemoteTable
1683+ localNode2 <- newLocalNode testTransport initRemoteTable
1684+
1685+ _ <- forkProcess localNode1 $ do
1686+ getSelfPid >>= register "foobar"
1687+ liftIO $ putMVar clientDone ()
1688+ expect >>= liftIO . putMVar clientDone
1689+
1690+ takeMVar clientDone
1691+ void $ runProcess localNode2 $ do
1692+ unsafeNSendRemote (localNodeId localNode1) "foobar" ()
1693+
1694+ takeMVar clientDone
1695+
1696+testUnsafeSendChan :: TestTransport -> Assertion
1697+testUnsafeSendChan TestTransport{..} = do
1698+ serverAddr <- newEmptyMVar
1699+ clientDone <- newEmptyMVar
1700+
1701+ localNode <- newLocalNode testTransport initRemoteTable
1702+ void $ forkProcess localNode $ do
1703+ self <- getSelfPid
1704+ liftIO $ putMVar serverAddr self
1705+ sp <- expect
1706+ unsafeSendChan sp ()
1707+
1708+ void $ forkProcess localNode $ do
1709+ serverPid <- liftIO $ takeMVar serverAddr
1710+ (sp, rp) <- newChan
1711+ unsafeSend serverPid sp
1712+ receiveChan rp :: Process ()
1713+ liftIO $ putMVar clientDone ()
1714+
1715+ takeMVar clientDone
1716+
1717+testCallLocal :: TestTransport -> Assertion
1718+testCallLocal TestTransport{..} = do
1719+ node <- newLocalNode testTransport initRemoteTable
1720+
1721+ -- Testing that (/=) <$> getSelfPid <*> callLocal getSelfPid.
1722+ result <- newEmptyMVar
1723+ runProcess node $ do
1724+ r <- (/=) <$> getSelfPid <*> callLocal getSelfPid
1725+ liftIO $ putMVar result r
1726+
1727+ takeMVar result >>= assertBool "Expected 'True'"
1728+
1729+ -- Testing that when callLocal is interrupted, the worker is interrupted.
1730+ ibox <- newIORef False
1731+ runProcess node $ do
1732+ keeper <- getSelfPid
1733+ spawnLocal $ do
1734+ caller <- getSelfPid
1735+ send keeper caller
1736+ onException
1737+ (callLocal $ do
1738+ onException (do send keeper caller
1739+ expect)
1740+ (do liftIO $ writeIORef ibox True))
1741+ (send keeper ())
1742+ caller <- expect
1743+ exit caller "test"
1744+ expect :: Process ()
1745+
1746+ readIORef ibox >>= assertBool "Expected 'True'"
1747+
1748+ -- Testing that when the worker raises an exception, the exception is propagated to the parent.
1749+ ibox2 <- newIORef False
1750+ runProcess node $ do
1751+ r <- try (callLocal $ error "e" >> return ())
1752+ liftIO $ writeIORef ibox2 $ case r of
1753+ Left (ErrorCall "e") -> True
1754+ _ -> False
1755+
1756+ readIORef ibox >>= assertBool "Expected 'True'"
1757+
1758+ -- Test that caller waits for the worker in correct situation
1759+ ibox3 <- newIORef False
1760+ result3 <- newEmptyMVar
1761+ runProcess node $ do
1762+ keeper <- getSelfPid
1763+ spawnLocal $ do
1764+ callLocal $
1765+ (do us <- getSelfPid
1766+ send keeper us
1767+ () <- expect
1768+ liftIO yield)
1769+ `finally` (liftIO $ writeIORef ibox3 True)
1770+ liftIO $ putMVar result3 =<< readIORef ibox3
1771+ worker <- expect
1772+ send worker ()
1773+
1774+ takeMVar result3 >>= assertBool "Expected 'True'"
1775+
1776+ -- Test that caller waits for the worker in case when caller gets an exception
1777+ ibox4 <- newIORef False
1778+ result4 <- newEmptyMVar
1779+ runProcess node $ do
1780+ keeper <- getSelfPid
1781+ spawnLocal $ do
1782+ caller <- getSelfPid
1783+ callLocal
1784+ ((do send keeper caller
1785+ expect)
1786+ `finally` (liftIO $ writeIORef ibox4 True))
1787+ `finally` (liftIO $ putMVar result4 =<< readIORef ibox4)
1788+ caller <- expect
1789+ exit caller "hi!"
1790+
1791+ takeMVar result4 >>= assertBool "Expected 'True'"
1792+ -- XXX: Testing that when mask_ $ callLocal p runs p in masked state.
1793+
1794+tests :: TestTransport -> IO [Test]
1795+tests testtrans = return [
1796+ testGroup "Basic features" [
1797+ testCase "Ping" (testPing testtrans)
1798+ , testCase "Math" (testMath testtrans)
1799+ , testCase "Timeout" (testTimeout testtrans)
1800+ , testCase "Timeout0" (testTimeout0 testtrans)
1801+ , testCase "SendToTerminated" (testSendToTerminated testtrans)
1802+ , testCase "TypedChannnels" (testTypedChannels testtrans)
1803+ , testCase "Terminate" (testTerminate testtrans)
1804+ , testCase "RegistryRemoteProcess" (testRegistryRemoteProcess testtrans)
1805+ , testCase "RemoteRegistry" (testRemoteRegistry testtrans)
1806+ , testCase "RemoteRegistryRemoteProcess" (testRemoteRegistryRemoteProcess testtrans)
1807+ , testCase "SpawnLocal" (testSpawnLocal testtrans)
1808+ , testCase "SpawnAsyncStrictness" (testSpawnAsyncStrictness testtrans)
1809+ , testCase "HandleMessageIf" (testHandleMessageIf testtrans)
1810+ , testCase "MatchAny" (testMatchAny testtrans)
1811+ , testCase "MatchAnyHandle" (testMatchAnyHandle testtrans)
1812+ , testCase "MatchAnyNoHandle" (testMatchAnyNoHandle testtrans)
1813+ , testCase "MatchAnyIf" (testMatchAnyIf testtrans)
1814+ , testCase "MatchMessageUnwrap" (testMatchMessageWithUnwrap testtrans)
1815+ , testCase "ReceiveChanTimeout" (testReceiveChanTimeout testtrans)
1816+ , testCase "ReceiveChanFeatures" (testReceiveChanFeatures testtrans)
1817+ , testCase "ChanLifecycle" (testChanLifecycle testtrans)
1818+ , testCase "KillLocal" (testKillLocal testtrans)
1819+ , testCase "KillRemote" (testKillRemote testtrans)
1820+ , testCase "Die" (testDie testtrans)
1821+ , testCase "PrettyExit" (testPrettyExit testtrans)
1822+ , testCase "CatchesExit" (testCatchesExit testtrans)
1823+ , testCase "Catches" (testCatches testtrans)
1824+ , testCase "MaskRestoreScope" (testMaskRestoreScope testtrans)
1825+ , testCase "ExitLocal" (testExitLocal testtrans)
1826+ , testCase "ExitRemote" (testExitRemote testtrans)
1827+ , testCase "RegistryMonitoring" (testRegistryMonitoring testtrans)
1828+ , testCase "TextCallLocal" (testCallLocal testtrans)
1829+ -- Unsafe Primitives
1830+ , testCase "TestUnsafeSend" (testUnsafeSend testtrans)
1831+ , testCase "TestUnsafeUSend" (testUnsafeUSend testtrans)
1832+ , testCase "TestUnsafeNSend" (testUnsafeNSend testtrans)
1833+ , testCase "TestUnsafeNSendRemote" (testUnsafeNSendRemote testtrans)
1834+ , testCase "TestUnsafeSendChan" (testUnsafeSendChan testtrans)
1835+ -- usend
1836+ , testCase "USend" (testUSend usend testtrans 50)
1837+ , testCase "UForward"
1838+ (testUSend (\p m -> uforward (createUnencodedMessage m) p)
1839+ testtrans 50
1840+ )
1841+ ]
1842+ , testGroup "Monitoring and Linking" [
1843+ -- Monitoring processes
1844+ --
1845+ -- The "missing" combinations in the list below don't make much sense, as
1846+ -- we cannot guarantee that the monitor reply or link exception will not
1847+ -- happen before the unmonitor or unlink
1848+ testCase "MonitorNormalTermination" (testMonitorNormalTermination testtrans True False)
1849+ , testCase "MonitorAbnormalTermination" (testMonitorAbnormalTermination testtrans True False)
1850+ , testCase "MonitorLocalDeadProcess" (testMonitorLocalDeadProcess testtrans True False)
1851+ , testCase "MonitorRemoteDeadProcess" (testMonitorRemoteDeadProcess testtrans True False)
1852+ , testCase "MonitorDisconnect" (testMonitorDisconnect testtrans True False)
1853+ , testCase "LinkUnreachable" (testMonitorUnreachable testtrans False False)
1854+ , testCase "LinkNormalTermination" (testMonitorNormalTermination testtrans False False)
1855+ , testCase "LinkAbnormalTermination" (testMonitorAbnormalTermination testtrans False False)
1856+ , testCase "LinkLocalDeadProcess" (testMonitorLocalDeadProcess testtrans False False)
1857+ , testCase "LinkRemoteDeadProcess" (testMonitorRemoteDeadProcess testtrans False False)
1858+ , testCase "LinkDisconnect" (testMonitorDisconnect testtrans False False)
1859+ , testCase "UnmonitorNormalTermination" (testMonitorNormalTermination testtrans True True)
1860+ , testCase "UnmonitorAbnormalTermination" (testMonitorAbnormalTermination testtrans True True)
1861+ , testCase "UnmonitorDisconnect" (testMonitorDisconnect testtrans True True)
1862+ , testCase "UnlinkNormalTermination" (testMonitorNormalTermination testtrans False True)
1863+ , testCase "UnlinkAbnormalTermination" (testMonitorAbnormalTermination testtrans False True)
1864+ , testCase "UnlinkDisconnect" (testMonitorDisconnect testtrans False True)
1865+ -- Monitoring nodes and channels
1866+ , testCase "MonitorNode" (testMonitorNode testtrans)
1867+ , testCase "MonitorLiveNode" (testMonitorLiveNode testtrans)
1868+ , testCase "MonitorChannel" (testMonitorChannel testtrans)
1869+ -- Reconnect
1870+ ]
1871+
1872+ -- Tests that fail occasionally and should be revised
1873+ , testGroup "Flaky" [
1874+ testCase "Reconnect" (testReconnect testtrans)
1875+ , testCase "Registry" (testRegistry testtrans)
1876+ , testCase "MergeChannels" (testMergeChannels testtrans)
1877+ , testCase "MonitorUnreachable" (testMonitorUnreachable testtrans True False)
1878+ ]
1879+ ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Closure.hsnew file+589
@@ -0,0 +1,589 @@
1+{-# LANGUAGE TemplateHaskell, KindSignatures #-}
2+module Control.Distributed.Process.Tests.Closure (tests) where
3+
4+import Network.Transport.Test (TestTransport(..))
5+
6+import Data.ByteString.Lazy (empty)
7+import Data.IORef
8+import Data.Typeable (Typeable)
9+import Data.Maybe
10+import Control.Monad (join, replicateM, forever, replicateM_, void, when, unless)
11+import Control.Exception (IOException, throw)
12+import Control.Concurrent (forkIO, threadDelay)
13+import Control.Concurrent.MVar
14+ ( MVar
15+ , newEmptyMVar
16+ , readMVar
17+ , takeMVar
18+ , putMVar
19+ , modifyMVar_
20+ , newMVar
21+ )
22+import Control.Applicative ((<$>))
23+import System.Random (randomIO)
24+import Control.Distributed.Process
25+import Control.Distributed.Process.Closure
26+import Control.Distributed.Process.Node
27+import Control.Distributed.Process.Internal.Types
28+ ( NodeId(nodeAddress)
29+ , createMessage
30+ , messageToPayload
31+ )
32+import Control.Distributed.Static (staticLabel, staticClosure)
33+import qualified Network.Transport as NT
34+
35+import Test.HUnit (Assertion)
36+import Test.Framework (Test)
37+import Test.Framework.Providers.HUnit (testCase)
38+
39+--------------------------------------------------------------------------------
40+-- Supporting definitions --
41+--------------------------------------------------------------------------------
42+
43+quintuple :: a -> b -> c -> d -> e -> (a, b, c, d, e)
44+quintuple a b c d e = (a, b, c, d, e)
45+
46+sdictInt :: SerializableDict Int
47+sdictInt = SerializableDict
48+
49+factorial :: Int -> Process Int
50+factorial 0 = return 1
51+factorial n = (n *) <$> factorial (n - 1)
52+
53+addInt :: Int -> Int -> Int
54+addInt x y = x + y
55+
56+putInt :: Int -> MVar Int -> IO ()
57+putInt = flip putMVar
58+
59+sendPid :: ProcessId -> Process ()
60+sendPid toPid = do
61+ fromPid <- getSelfPid
62+ send toPid fromPid
63+
64+wait :: Int -> Process ()
65+wait = liftIO . threadDelay
66+
67+expectUnit :: Process ()
68+expectUnit = expect
69+
70+isPrime :: Integer -> Process Bool
71+isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..]
72+ where
73+ sieve :: [Integer] -> [Integer]
74+ sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]
75+ sieve [] = error "Uh oh -- we've run out of primes"
76+
77+-- | First argument indicates empty closure environment
78+typedPingServer :: () -> ReceivePort (SendPort ()) -> Process ()
79+typedPingServer () rport = forever $ do
80+ sport <- receiveChan rport
81+ sendChan sport ()
82+
83+signal :: ProcessId -> Process ()
84+signal pid = send pid ()
85+
86+remotable [ 'factorial
87+ , 'addInt
88+ , 'putInt
89+ , 'sendPid
90+ , 'sdictInt
91+ , 'wait
92+ , 'expectUnit
93+ , 'typedPingServer
94+ , 'isPrime
95+ , 'quintuple
96+ , 'signal
97+ ]
98+
99+randomElement :: [a] -> IO a
100+randomElement xs = do
101+ ix <- randomIO
102+ return (xs !! (ix `mod` length xs))
103+
104+remotableDecl [
105+ [d| dfib :: ([NodeId], SendPort Integer, Integer) -> Process () ;
106+ dfib (_, reply, 0) = sendChan reply 0
107+ dfib (_, reply, 1) = sendChan reply 1
108+ dfib (nids, reply, n) = do
109+ nid1 <- liftIO $ randomElement nids
110+ nid2 <- liftIO $ randomElement nids
111+ (sport, rport) <- newChan
112+ spawn nid1 $ $(mkClosure 'dfib) (nids, sport, n - 2)
113+ spawn nid2 $ $(mkClosure 'dfib) (nids, sport, n - 1)
114+ n1 <- receiveChan rport
115+ n2 <- receiveChan rport
116+ sendChan reply $ n1 + n2
117+ |]
118+ ]
119+
120+-- Just try creating a static polymorphic value
121+staticQuintuple :: (Typeable a, Typeable b, Typeable c, Typeable d, Typeable e)
122+ => Static (a -> b -> c -> d -> e -> (a, b, c, d, e))
123+staticQuintuple = $(mkStatic 'quintuple)
124+
125+factorialClosure :: Int -> Closure (Process Int)
126+factorialClosure = $(mkClosure 'factorial)
127+
128+addIntClosure :: Int -> Closure (Int -> Int)
129+addIntClosure = $(mkClosure 'addInt)
130+
131+putIntClosure :: Int -> Closure (MVar Int -> IO ())
132+putIntClosure = $(mkClosure 'putInt)
133+
134+sendPidClosure :: ProcessId -> Closure (Process ())
135+sendPidClosure = $(mkClosure 'sendPid)
136+
137+sendFac :: Int -> ProcessId -> Closure (Process ())
138+sendFac n pid = factorialClosure n `bindCP` cpSend $(mkStatic 'sdictInt) pid
139+
140+factorialOf :: Closure (Int -> Process Int)
141+factorialOf = staticClosure $(mkStatic 'factorial)
142+
143+factorial' :: Int -> Closure (Process Int)
144+factorial' n = returnCP $(mkStatic 'sdictInt) n `bindCP` factorialOf
145+
146+waitClosure :: Int -> Closure (Process ())
147+waitClosure = $(mkClosure 'wait)
148+
149+simulateNetworkFailure :: TestTransport -> LocalNode -> LocalNode -> Process ()
150+simulateNetworkFailure TestTransport{..} from to = liftIO $ do
151+ m <- newEmptyMVar
152+ _ <- forkProcess to $ getSelfPid >>= liftIO . putMVar m
153+ runProcess from $ do
154+ them <- liftIO $ takeMVar m
155+ pinger <- spawnLocal $ forever $ send them ()
156+ _ <- monitorNode (localNodeId to)
157+ liftIO $ testBreakConnection (nodeAddress $ localNodeId from)
158+ (nodeAddress $ localNodeId to)
159+ NodeMonitorNotification _ _ _ <- expect
160+ kill pinger "finished"
161+ return ()
162+
163+--------------------------------------------------------------------------------
164+-- The tests proper --
165+--------------------------------------------------------------------------------
166+
167+testUnclosure :: TestTransport -> RemoteTable -> Assertion
168+testUnclosure TestTransport{..} rtable = do
169+ node <- newLocalNode testTransport rtable
170+ done <- newEmptyMVar
171+ forkProcess node $ do
172+ i <- join . unClosure $ factorialClosure 5
173+ liftIO $ putMVar done ()
174+ if i == 720
175+ then return ()
176+ else error "Something went horribly wrong"
177+ takeMVar done
178+
179+testBind :: TestTransport -> RemoteTable -> Assertion
180+testBind TestTransport{..} rtable = do
181+ node <- newLocalNode testTransport rtable
182+ done <- newEmptyMVar
183+ runProcess node $ do
184+ us <- getSelfPid
185+ join . unClosure $ sendFac 6 us
186+ (i :: Int) <- expect
187+ liftIO $ putMVar done ()
188+ if i == 720
189+ then return ()
190+ else error "Something went horribly wrong"
191+ takeMVar done
192+
193+testSendPureClosure :: TestTransport -> RemoteTable -> Assertion
194+testSendPureClosure TestTransport{..} rtable = do
195+ serverAddr <- newEmptyMVar
196+ serverDone <- newEmptyMVar
197+
198+ forkIO $ do
199+ node <- newLocalNode testTransport rtable
200+ addr <- forkProcess node $ do
201+ cl <- expect
202+ fn <- unClosure cl :: Process (Int -> Int)
203+ (_ :: Int) <- return $ fn 6
204+ liftIO $ putMVar serverDone ()
205+ putMVar serverAddr addr
206+
207+ forkIO $ do
208+ node <- newLocalNode testTransport rtable
209+ theirAddr <- readMVar serverAddr
210+ runProcess node $ send theirAddr (addIntClosure 7)
211+
212+ takeMVar serverDone
213+
214+testSendIOClosure :: TestTransport -> RemoteTable -> Assertion
215+testSendIOClosure TestTransport{..} rtable = do
216+ serverAddr <- newEmptyMVar
217+ serverDone <- newEmptyMVar
218+
219+ forkIO $ do
220+ node <- newLocalNode testTransport rtable
221+ addr <- forkProcess node $ do
222+ cl <- expect
223+ io <- unClosure cl :: Process (MVar Int -> IO ())
224+ liftIO $ do
225+ someMVar <- newEmptyMVar
226+ io someMVar
227+ i <- readMVar someMVar
228+ putMVar serverDone ()
229+ if i == 5
230+ then return ()
231+ else error "Something went horribly wrong"
232+ putMVar serverAddr addr
233+
234+ forkIO $ do
235+ node <- newLocalNode testTransport rtable
236+ theirAddr <- readMVar serverAddr
237+ runProcess node $ send theirAddr (putIntClosure 5)
238+
239+ takeMVar serverDone
240+
241+testSendProcClosure :: TestTransport -> RemoteTable -> Assertion
242+testSendProcClosure TestTransport{..} rtable = do
243+ serverAddr <- newEmptyMVar
244+ clientDone <- newEmptyMVar
245+
246+ forkIO $ do
247+ node <- newLocalNode testTransport rtable
248+ addr <- forkProcess node $ do
249+ cl <- expect
250+ pr <- unClosure cl :: Process (Int -> Process ())
251+ pr 5
252+ putMVar serverAddr addr
253+
254+ forkIO $ do
255+ node <- newLocalNode testTransport rtable
256+ theirAddr <- readMVar serverAddr
257+ runProcess node $ do
258+ pid <- getSelfPid
259+ send theirAddr (cpSend $(mkStatic 'sdictInt) pid)
260+ i <- expect :: Process Int
261+ if i == 5
262+ then liftIO $ putMVar clientDone ()
263+ else error "Something went horribly wrong"
264+
265+ takeMVar clientDone
266+
267+testSpawn :: TestTransport -> RemoteTable -> Assertion
268+testSpawn TestTransport{..} rtable = do
269+ serverNodeAddr <- newEmptyMVar
270+ clientDone <- newEmptyMVar
271+
272+ forkIO $ do
273+ node <- newLocalNode testTransport rtable
274+ putMVar serverNodeAddr (localNodeId node)
275+
276+ forkIO $ do
277+ node <- newLocalNode testTransport rtable
278+ nid <- readMVar serverNodeAddr
279+ runProcess node $ do
280+ pid <- getSelfPid
281+ pid' <- spawn nid (sendPidClosure pid)
282+ pid'' <- expect
283+ if pid' == pid''
284+ then liftIO $ putMVar clientDone ()
285+ else error "Something went horribly wrong"
286+
287+ takeMVar clientDone
288+
289+-- | Tests that spawn executes the supplied closure even if the caller dies
290+-- immediately after calling spawn.
291+--
292+-- This situation is of interest because the implementation of spawn has the
293+-- remote peer monitor the caller. See DP-99.
294+--
295+-- The condition is tested by using a transport which refuses to send to the
296+-- remote peer the message that it is waiting to stop monitoring the caller,
297+-- namely @()@.
298+--
299+testSpawnRace :: TestTransport -> RemoteTable -> Assertion
300+testSpawnRace TestTransport{..} rtable = do
301+ node1 <- newLocalNode (wrapTransport testTransport) rtable
302+ node2 <- newLocalNode testTransport rtable
303+
304+ runProcess node1 $ do
305+ pid <- getSelfPid
306+ spawnLocal $ spawn (localNodeId node2) (sendPidClosure pid) >>= send pid
307+ pid' <- expect :: Process ProcessId
308+ pid'' <- expect :: Process ProcessId
309+ if pid' == pid''
310+ then return ()
311+ else error "Something went horribly wrong"
312+
313+ where
314+
315+ wrapTransport (NT.Transport ne ct) = NT.Transport (fmap (fmap wrapEP) ne) ct
316+
317+ wrapEP :: NT.EndPoint -> NT.EndPoint
318+ wrapEP e =
319+ e { NT.connect = \x y z -> do
320+ healthy <- newIORef True
321+ fmap (fmap $ wrapConnection healthy e x) $ NT.connect e x y z
322+ }
323+
324+ wrapConnection :: IORef Bool -> NT.EndPoint -> NT.EndPointAddress
325+ -> NT.Connection -> NT.Connection
326+ wrapConnection healthy e remoteAddr (NT.Connection s closeC) =
327+ flip NT.Connection closeC $ \msg -> do
328+ when (msg == messageToPayload (createMessage ())) $ do
329+ writeIORef healthy False
330+ testBreakConnection (NT.address e) remoteAddr
331+ isHealthy <- readIORef healthy
332+ if isHealthy then s msg
333+ else return $ Left $ NT.TransportError NT.SendFailed ""
334+
335+testCall :: TestTransport -> RemoteTable -> Assertion
336+testCall TestTransport{..} rtable = do
337+ serverNodeAddr <- newEmptyMVar
338+ clientDone <- newEmptyMVar
339+
340+ forkIO $ do
341+ node <- newLocalNode testTransport rtable
342+ putMVar serverNodeAddr (localNodeId node)
343+
344+ forkIO $ do
345+ node <- newLocalNode testTransport rtable
346+ nid <- readMVar serverNodeAddr
347+ runProcess node $ do
348+ (a :: Int) <- call $(mkStatic 'sdictInt) nid (factorialClosure 5)
349+ if a == 120
350+ then liftIO $ putMVar clientDone ()
351+ else error "something went horribly wrong"
352+
353+ takeMVar clientDone
354+
355+testCallBind :: TestTransport -> RemoteTable -> Assertion
356+testCallBind TestTransport{..} rtable = do
357+ serverNodeAddr <- newEmptyMVar
358+ clientDone <- newEmptyMVar
359+
360+ forkIO $ do
361+ node <- newLocalNode testTransport rtable
362+ putMVar serverNodeAddr (localNodeId node)
363+
364+ forkIO $ do
365+ node <- newLocalNode testTransport rtable
366+ nid <- readMVar serverNodeAddr
367+ runProcess node $ do
368+ (a :: Int) <- call $(mkStatic 'sdictInt) nid (factorial' 5)
369+ if a == 120
370+ then liftIO $ putMVar clientDone ()
371+ else error "Something went horribly wrong"
372+
373+ takeMVar clientDone
374+
375+testSeq :: TestTransport -> RemoteTable -> Assertion
376+testSeq TestTransport{..} rtable = do
377+ node <- newLocalNode testTransport rtable
378+ done <- newEmptyMVar
379+ runProcess node $ do
380+ us <- getSelfPid
381+ join . unClosure $ sendFac 5 us `seqCP` sendFac 6 us
382+ a :: Int <- expect
383+ b :: Int <- expect
384+ if a == 120 && b == 720
385+ then liftIO $ putMVar done ()
386+ else error "Something went horribly wrong"
387+ takeMVar done
388+
389+-- Test 'spawnSupervised'
390+--
391+-- Set up a supervisor, spawn a child, then have a third process monitor the
392+-- child. The supervisor then throws an exception, the child dies because it
393+-- was linked to the supervisor, and the third process notices that the child
394+-- dies.
395+testSpawnSupervised :: TestTransport -> RemoteTable -> Assertion
396+testSpawnSupervised TestTransport{..} rtable = do
397+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
398+ [superPid, childPid] <- replicateM 2 $ newEmptyMVar
399+ thirdProcessDone <- newEmptyMVar
400+ linkUp <- newEmptyMVar
401+
402+ forkProcess node1 $ do
403+ us <- getSelfPid
404+ liftIO $ putMVar superPid us
405+ (child, _ref) <- spawnSupervised (localNodeId node2)
406+ (sendPidClosure us `seqCP` $(mkStaticClosure 'expectUnit))
407+ _ <- expect :: Process ProcessId
408+
409+ liftIO $ do putMVar childPid child
410+ -- Give the child a chance to link to us
411+ takeMVar linkUp
412+ throw supervisorDeath
413+
414+ forkProcess node2 $ do
415+ res <- liftIO $ mapM readMVar [superPid, childPid]
416+ case res of
417+ [super, child] -> do
418+ ref <- monitor child
419+ self <- getSelfPid
420+ let waitForMOrL = do
421+ liftIO $ threadDelay 10000
422+ mpinfo <- getProcessInfo child
423+ case mpinfo of
424+ Nothing -> waitForMOrL
425+ Just pinfo ->
426+ unless (isJust $ lookup self (infoMonitors pinfo)) waitForMOrL
427+ waitForMOrL
428+ liftIO $ putMVar linkUp ()
429+ -- because monitor message was sent before message to process
430+ -- we hope that it will be processed before
431+ res' <- expect
432+ case res' of
433+ (ProcessMonitorNotification ref' pid' (DiedException e)) ->
434+ if (ref' == ref && pid' == child &&
435+ e == show (ProcessLinkException super
436+ (DiedException (show supervisorDeath))))
437+ then liftIO $ putMVar thirdProcessDone ()
438+ else error "Something went horribly wrong"
439+ _ -> error "Something went horribly wrong"
440+
441+ _ -> die $ "Something went horribly wrong"
442+
443+ takeMVar thirdProcessDone
444+ where
445+ supervisorDeath :: IOException
446+ supervisorDeath = userError "Supervisor died"
447+
448+testSpawnInvalid :: TestTransport -> RemoteTable -> Assertion
449+testSpawnInvalid TestTransport{..} rtable = do
450+ node <- newLocalNode testTransport rtable
451+ done <- newEmptyMVar
452+ forkProcess node $ do
453+ (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)
454+ ProcessMonitorNotification ref' pid' _reason <- expect
455+ -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure
456+ res <- return $ ref' == ref && pid == pid'
457+ if res == True
458+ then liftIO $ putMVar done ()
459+ else error "Something went horribly wrong"
460+ takeMVar done
461+
462+testClosureExpect :: TestTransport -> RemoteTable -> Assertion
463+testClosureExpect TestTransport{..} rtable = do
464+ node <- newLocalNode testTransport rtable
465+ done <- newEmptyMVar
466+ runProcess node $ do
467+ nodeId <- getSelfNode
468+ us <- getSelfPid
469+ them <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us
470+ send them (1234 :: Int)
471+ (res :: Int) <- expect
472+ if res == 1234
473+ then liftIO $ putMVar done ()
474+ else error "Something went horribly wrong"
475+ takeMVar done
476+
477+testSpawnChannel :: TestTransport -> RemoteTable -> Assertion
478+testSpawnChannel TestTransport{..} rtable = do
479+ done <- newEmptyMVar
480+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
481+
482+ forkProcess node1 $ do
483+ pingServer <- spawnChannel
484+ (sdictSendPort sdictUnit)
485+ (localNodeId node2)
486+ ($(mkClosure 'typedPingServer) ())
487+ (sendReply, receiveReply) <- newChan
488+ sendChan pingServer sendReply
489+ receiveChan receiveReply
490+ liftIO $ putMVar done ()
491+
492+ takeMVar done
493+
494+testTDict :: TestTransport -> RemoteTable -> Assertion
495+testTDict TestTransport{..} rtable = do
496+ done <- newEmptyMVar
497+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
498+ forkProcess node1 $ do
499+ res <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))
500+ if res == True
501+ then liftIO $ putMVar done ()
502+ else error "Something went horribly wrong..."
503+ takeMVar done
504+
505+testFib :: TestTransport -> RemoteTable -> Assertion
506+testFib TestTransport{..} rtable = do
507+ nodes <- replicateM 4 $ newLocalNode testTransport rtable
508+ done <- newEmptyMVar
509+
510+ forkProcess (head nodes) $ do
511+ (sport, rport) <- newChan
512+ spawnLocal $ dfib (map localNodeId nodes, sport, 10)
513+ ff <- receiveChan rport :: Process Integer
514+ liftIO $ putMVar done ()
515+ if ff /= 55
516+ then die $ "Something went horribly wrong"
517+ else return ()
518+
519+ takeMVar done
520+
521+testSpawnReconnect :: TestTransport -> RemoteTable -> Assertion
522+testSpawnReconnect testtrans@TestTransport{..} rtable = do
523+ [node1, node2] <- replicateM 2 $ newLocalNode testTransport rtable
524+ let nid1 = localNodeId node1
525+ -- nid2 = localNodeId node2
526+ done <- newEmptyMVar
527+ iv <- newMVar (0 :: Int)
528+
529+ incr <- forkProcess node1 $ forever $ do
530+ () <- expect
531+ liftIO $ modifyMVar_ iv (return . (+ 1))
532+
533+ forkProcess node2 $ do
534+ _pid1 <- spawn nid1 ($(mkClosure 'signal) incr)
535+ simulateNetworkFailure testtrans node2 node1
536+ _pid2 <- spawn nid1 ($(mkClosure 'signal) incr)
537+ _pid3 <- spawn nid1 ($(mkClosure 'signal) incr)
538+
539+ liftIO $ threadDelay 100000
540+
541+ count <- liftIO $ takeMVar iv
542+ res <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'
543+
544+ liftIO $ putMVar done ()
545+ if res /= True
546+ then error "Something went horribly wrong"
547+ else return ()
548+
549+ takeMVar done
550+
551+-- | 'spawn' used to ave a race condition which would be triggered if the
552+-- spawning process terminates immediately after spawning
553+testSpawnTerminate :: TestTransport -> RemoteTable -> Assertion
554+testSpawnTerminate TestTransport{..} rtable = do
555+ slave <- newLocalNode testTransport rtable
556+ master <- newLocalNode testTransport rtable
557+ masterDone <- newEmptyMVar
558+
559+ runProcess master $ do
560+ us <- getSelfPid
561+ replicateM_ 1000 . spawnLocal . void . spawn (localNodeId slave) $ $(mkClosure 'signal) us
562+ replicateM_ 1000 $ (expect :: Process ())
563+ liftIO $ putMVar masterDone ()
564+
565+ takeMVar masterDone
566+
567+tests :: TestTransport -> IO [Test]
568+tests testtrans = do
569+ let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable
570+ return
571+ [ testCase "Unclosure" (testUnclosure testtrans rtable)
572+ , testCase "Bind" (testBind testtrans rtable)
573+ , testCase "SendPureClosure" (testSendPureClosure testtrans rtable)
574+ , testCase "SendIOClosure" (testSendIOClosure testtrans rtable)
575+ , testCase "SendProcClosure" (testSendProcClosure testtrans rtable)
576+ , testCase "Spawn" (testSpawn testtrans rtable)
577+ , testCase "SpawnRace" (testSpawnRace testtrans rtable)
578+ , testCase "Call" (testCall testtrans rtable)
579+ , testCase "CallBind" (testCallBind testtrans rtable)
580+ , testCase "Seq" (testSeq testtrans rtable)
581+ , testCase "SpawnSupervised" (testSpawnSupervised testtrans rtable)
582+ , testCase "SpawnInvalid" (testSpawnInvalid testtrans rtable)
583+ , testCase "ClosureExpect" (testClosureExpect testtrans rtable)
584+ , testCase "SpawnChannel" (testSpawnChannel testtrans rtable)
585+ , testCase "TDict" (testTDict testtrans rtable)
586+ , testCase "Fib" (testFib testtrans rtable)
587+ , testCase "SpawnTerminate" (testSpawnTerminate testtrans rtable)
588+ , testCase "SpawnReconnect" (testSpawnReconnect testtrans rtable)
589+ ]
distributed-process-tests/src/Control/Distributed/Process/Tests/Internal/Utils.hsnew file+243
@@ -0,0 +1,243 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE TemplateHaskell #-}
4+{-# LANGUAGE CPP #-}
5+
6+-----------------------------------------------------------------------------
7+-- |
8+-- Module : Control.Distributed.Process.Tests.Internal.Utils
9+-- Copyright : (c) Tim Watson, Jeff Epstein 2013
10+-- License : BSD3 (see the file LICENSE)
11+--
12+-- Maintainer : Tim Watson
13+-- Stability : experimental
14+-- Portability : non-portable (requires concurrency)
15+--
16+-- This module provides basic building blocks for testing Cloud Haskell programs.
17+-----------------------------------------------------------------------------
18+module Control.Distributed.Process.Tests.Internal.Utils
19+ ( TestResult
20+ -- ping !
21+ , Ping(Ping)
22+ , ping
23+ , pause
24+ , shouldBe
25+ , shouldMatch
26+ , shouldContain
27+ , shouldNotContain
28+ , expectThat
29+ , synchronisedAssertion
30+ -- test process utilities
31+ , TestProcessControl
32+ , startTestProcess
33+ , runTestProcess
34+ , testProcessGo
35+ , testProcessStop
36+ , testProcessReport
37+ , delayedAssertion
38+ , assertComplete
39+ -- logging
40+ , Logger()
41+ , newLogger
42+ , putLogMsg
43+ , stopLogger
44+ -- runners
45+ , tryRunProcess
46+ , tryForkProcess
47+ , noop
48+ , stash
49+ ) where
50+
51+#if ! MIN_VERSION_base(4,6,0)
52+import Prelude hiding (catch)
53+#endif
54+import Control.Concurrent
55+ ( ThreadId
56+ , myThreadId
57+ , forkIO
58+ )
59+import Control.Concurrent.STM
60+ ( TQueue
61+ , newTQueueIO
62+ , readTQueue
63+ , writeTQueue
64+ )
65+import Control.Concurrent.MVar
66+ ( MVar
67+ , newEmptyMVar
68+ , takeMVar
69+ )
70+
71+import Control.Concurrent
72+ ( throwTo
73+ )
74+import Control.Concurrent.MVar
75+ ( putMVar
76+ )
77+import Control.Distributed.Process hiding (finally, catch)
78+import Control.Distributed.Process.Node
79+import Control.Distributed.Process.Serializable()
80+
81+import Control.Exception (AsyncException(ThreadKilled), SomeException)
82+import Control.Monad (forever, void)
83+import Control.Monad.Catch (finally, catch)
84+import Control.Monad.STM (atomically)
85+import Control.Rematch hiding (match)
86+import Control.Rematch.Run
87+import Data.Binary
88+import Data.Typeable (Typeable)
89+
90+import Test.HUnit (Assertion, assertFailure)
91+import Test.HUnit.Base (assertBool)
92+
93+import GHC.Generics
94+import System.Timeout (timeout)
95+
96+-- | A mutable cell containing a test result.
97+type TestResult a = MVar a
98+
99+-- | A simple @Ping@ signal
100+data Ping = Ping
101+ deriving (Typeable, Generic, Eq, Show)
102+instance Binary Ping where
103+
104+ping :: ProcessId -> Process ()
105+ping pid = send pid Ping
106+
107+-- | Control signals used to manage /test processes/
108+data TestProcessControl = Stop | Go | Report ProcessId
109+ deriving (Typeable, Generic)
110+
111+instance Binary TestProcessControl where
112+
113+data Private = Private
114+ deriving (Typeable, Generic)
115+instance Binary Private where
116+
117+-- | Does exactly what it says on the tin, doing so in the @Process@ monad.
118+noop :: Process ()
119+noop = return ()
120+
121+pause :: Int -> Process ()
122+pause delay =
123+ void $ receiveTimeout delay [ match (\Private -> return ()) ]
124+
125+synchronisedAssertion :: Eq a
126+ => String
127+ -> LocalNode
128+ -> a
129+ -> (TestResult a -> Process ())
130+ -> MVar ()
131+ -> Assertion
132+synchronisedAssertion note localNode expected testProc lock = do
133+ result <- newEmptyMVar
134+ _ <- forkProcess localNode $ do
135+ acquire lock
136+ finally (testProc result)
137+ (release lock)
138+ assertComplete note result expected
139+ where acquire lock' = liftIO $ takeMVar lock'
140+ release lock' = liftIO $ putMVar lock' ()
141+
142+stash :: TestResult a -> a -> Process ()
143+stash mvar x = liftIO $ putMVar mvar x
144+
145+expectThat :: a -> Matcher a -> Process ()
146+expectThat a matcher = case res of
147+ MatchSuccess -> return ()
148+ (MatchFailure msg) -> liftIO $ assertFailure msg
149+ where res = runMatch matcher a
150+
151+shouldBe :: a -> Matcher a -> Process ()
152+shouldBe = expectThat
153+
154+shouldContain :: (Show a, Eq a) => [a] -> a -> Process ()
155+shouldContain xs x = expectThat xs $ hasItem (equalTo x)
156+
157+shouldNotContain :: (Show a, Eq a) => [a] -> a -> Process ()
158+shouldNotContain xs x = expectThat xs $ isNot (hasItem (equalTo x))
159+
160+shouldMatch :: a -> Matcher a -> Process ()
161+shouldMatch = expectThat
162+
163+-- | Run the supplied @testProc@ using an @MVar@ to collect and assert
164+-- against its result. Uses the supplied @note@ if the assertion fails.
165+delayedAssertion :: (Eq a) => String -> LocalNode -> a ->
166+ (TestResult a -> Process ()) -> Assertion
167+delayedAssertion note localNode expected testProc = do
168+ result <- newEmptyMVar
169+ _ <- forkProcess localNode $ testProc result
170+ assertComplete note result expected
171+
172+-- | Takes the value of @mv@ (using @takeMVar@) and asserts that it matches @a@
173+assertComplete :: (Eq a) => String -> MVar a -> a -> IO ()
174+assertComplete msg mv a = do
175+ b <- takeMVar mv
176+ assertBool msg (a == b)
177+
178+-- synchronised logging
179+
180+data Logger = Logger { _tid :: ThreadId, msgs :: TQueue String }
181+
182+-- | Create a new Logger.
183+-- Logger uses a 'TQueue' to receive and process messages on a worker thread.
184+newLogger :: IO Logger
185+newLogger = do
186+ tid <- liftIO $ myThreadId
187+ q <- liftIO $ newTQueueIO
188+ _ <- forkIO $ logger q
189+ return $ Logger tid q
190+ where logger q' = forever $ do
191+ msg <- atomically $ readTQueue q'
192+ putStrLn msg
193+
194+-- | Send a message to the Logger
195+putLogMsg :: Logger -> String -> Process ()
196+putLogMsg logger msg = liftIO $ atomically $ writeTQueue (msgs logger) msg
197+
198+-- | Stop the worker thread for the given Logger
199+stopLogger :: Logger -> IO ()
200+stopLogger = (flip throwTo) ThreadKilled . _tid
201+
202+-- | Starts a test process on the local node.
203+startTestProcess :: Process () -> Process ProcessId
204+startTestProcess proc =
205+ spawnLocal $ do
206+ getSelfPid >>= register "test-process"
207+ runTestProcess proc
208+
209+-- | Runs a /test process/ around the supplied @proc@, which is executed
210+-- whenever the outer process loop receives a 'Go' signal.
211+runTestProcess :: Process () -> Process ()
212+runTestProcess proc = do
213+ ctl <- expect
214+ case ctl of
215+ Stop -> return ()
216+ Go -> proc >> runTestProcess proc
217+ Report p -> receiveWait [matchAny (\m -> forward m p)] >> runTestProcess proc
218+
219+-- | Tell a /test process/ to continue executing
220+testProcessGo :: ProcessId -> Process ()
221+testProcessGo pid = send pid Go
222+
223+-- | Tell a /test process/ to stop (i.e., 'terminate')
224+testProcessStop :: ProcessId -> Process ()
225+testProcessStop pid = send pid Stop
226+
227+-- | Tell a /test process/ to send a report (message)
228+-- back to the calling process
229+testProcessReport :: ProcessId -> Process ()
230+testProcessReport pid = do
231+ self <- getSelfPid
232+ send pid $ Report self
233+
234+tryRunProcess :: LocalNode -> Process () -> IO ()
235+tryRunProcess node p = do
236+ tid <- liftIO myThreadId
237+ void $ timeout (1000000 * 60 * 5 :: Int) $
238+ runProcess node $ catch p (\e -> liftIO $ throwTo tid (e::SomeException))
239+
240+tryForkProcess :: LocalNode -> Process () -> IO ProcessId
241+tryForkProcess node p = do
242+ tid <- liftIO myThreadId
243+ forkProcess node $ catch p (\e -> liftIO $ throwTo tid (e::SomeException))
distributed-process-tests/src/Control/Distributed/Process/Tests/Mx.hsnew file+542
@@ -0,0 +1,542 @@
1+{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE ParallelListComp #-}
3+
4+module Control.Distributed.Process.Tests.Mx (tests) where
5+
6+import Control.Distributed.Process.Tests.Internal.Utils
7+import Network.Transport.Test (TestTransport(..))
8+import Control.Distributed.Process hiding (bracket, finally, try)
9+import Control.Distributed.Process.Internal.Types
10+ ( ProcessExitException(..)
11+ , unsafeCreateUnencodedMessage
12+ )
13+import Control.Distributed.Process.Node
14+import qualified Control.Distributed.Process.UnsafePrimitives as Unsafe
15+ ( send
16+ , nsend
17+ , nsendRemote
18+ , usend
19+ , sendChan
20+ )
21+import Control.Distributed.Process.Management
22+ ( MxEvent(..)
23+ , MxAgentId(..)
24+ , mxAgent
25+ , mxSink
26+ , mxReady
27+ , mxReceive
28+ , mxDeactivate
29+ , liftMX
30+ , mxGetLocal
31+ , mxSetLocal
32+ , mxUpdateLocal
33+ , mxNotify
34+ , mxBroadcast
35+ )
36+import Control.Monad (void, unless)
37+import Control.Monad.Catch(finally, bracket, try)
38+import Control.Rematch (equalTo)
39+import Data.Binary
40+import Data.List (find, sort, intercalate)
41+import Data.Maybe (isJust, fromJust, isNothing, fromMaybe, catMaybes)
42+import Data.Typeable
43+import GHC.Generics hiding (from)
44+#if ! MIN_VERSION_base(4,6,0)
45+import Prelude hiding (catch, log)
46+#endif
47+
48+import Test.Framework
49+ ( Test
50+ , testGroup
51+ )
52+import Test.Framework.Providers.HUnit (testCase)
53+
54+data Publish = Publish
55+ deriving (Typeable, Generic, Eq)
56+
57+instance Binary Publish where
58+
59+awaitExit :: ProcessId -> Process ()
60+awaitExit pid =
61+ withMonitorRef pid $ \ref -> do
62+ receiveWait
63+ [ matchIf (\(ProcessMonitorNotification r _ _) -> r == ref)
64+ (\_ -> return ())
65+ ]
66+ where
67+ withMonitorRef p = bracket (monitor p) unmonitor
68+
69+testAgentBroadcast :: TestResult (Maybe ()) -> Process ()
70+testAgentBroadcast result = do
71+ (resultSP, resultRP) <- newChan :: Process (SendPort (), ReceivePort ())
72+
73+ publisher <- mxAgent (MxAgentId "publisher-agent") () [
74+ mxSink $ \() -> mxBroadcast Publish >> mxReady
75+ ]
76+
77+ consumer <- mxAgent (MxAgentId "consumer-agent") () [
78+ mxSink $ \Publish -> (liftMX $ sendChan resultSP ()) >> mxReady
79+ ]
80+
81+ mxNotify ()
82+ -- Once the publisher has seen our message, it will broadcast the Publish
83+ -- and the consumer will see that and send the result to our typed channel.
84+ stash result =<< receiveChanTimeout 10000000 resultRP
85+
86+ kill publisher "finished"
87+ kill consumer "finished"
88+
89+testAgentDualInput :: TestResult (Maybe Int) -> Process ()
90+testAgentDualInput result = do
91+ (sp, rp) <- newChan
92+ s <- mxAgent (MxAgentId "sum-agent") (0 :: Int) [
93+ mxSink $ (\(i :: Int) -> do
94+ mxSetLocal . (+i) =<< mxGetLocal
95+ i' <- mxGetLocal
96+ if i' == 15
97+ then do mxGetLocal >>= liftMX . sendChan sp
98+ mxDeactivate "finished"
99+ else mxReady)
100+ ]
101+
102+ mRef <- monitor s
103+
104+ mxNotify (1 :: Int)
105+ nsend "sum-agent" (3 :: Int)
106+ mxNotify (2 :: Int)
107+ nsend "sum-agent" (4 :: Int)
108+ mxNotify (5 :: Int)
109+
110+ stash result =<< receiveChanTimeout 10000000 rp
111+ died <- receiveTimeout 10000000 [
112+ matchIf (\(ProcessMonitorNotification r _ _) -> r == mRef) (const $ return True)
113+ ]
114+ died `shouldBe` equalTo (Just True)
115+
116+testAgentPrioritisation :: TestResult [String] -> Process ()
117+testAgentPrioritisation result = do
118+
119+ -- TODO: this isn't really testing how we /prioritise/ one source
120+ -- over another at all, but I've not yet figured out the right way
121+ -- to do so, since we're at the whim of the scheduler with regards
122+ -- the timeliness of nsend versus mxNotify anyway.
123+
124+ let name = "prioritising-agent"
125+ (sp, rp) <- newChan
126+ s <- mxAgent (MxAgentId name) ["first"] [
127+ mxSink (\(s :: String) -> do
128+ mxUpdateLocal (s:)
129+ st <- mxGetLocal
130+ case length st of
131+ n | n == 5 -> do liftMX $ sendChan sp (sort st)
132+ mxDeactivate "finished"
133+ _ -> mxReceive -- go to the mailbox
134+ )
135+ ]
136+
137+ nsend name "second"
138+ mxNotify "third"
139+ mxNotify "fourth"
140+ nsend name "fifth"
141+
142+ stash result . fromMaybe [] =<< receiveChanTimeout 10000000 rp
143+ awaitExit s
144+
145+testAgentMailboxHandling :: TestResult (Maybe ()) -> Process ()
146+testAgentMailboxHandling result = do
147+ (sp, rp) <- newChan
148+ agent <- mxAgent (MxAgentId "mailbox-agent") () [
149+ mxSink $ \() -> (liftMX $ sendChan sp ()) >> mxReady
150+ ]
151+
152+ nsend "mailbox-agent" ()
153+
154+ stash result =<< receiveChanTimeout 1000000 rp
155+ kill agent "finished"
156+
157+testAgentEventHandling :: TestResult Bool -> Process ()
158+testAgentEventHandling result = do
159+ us <- getSelfPid
160+ -- because this test is a bit racy, let's ensure it can't run indefinitely
161+ timer <- spawnLocal $ do
162+ pause (1000000 * 60 * 5)
163+ -- okay we've waited 5 mins, let's kill the test off if it's stuck...
164+ stash result False
165+ kill us "Test Timed Out"
166+
167+ let initState = [] :: [MxEvent]
168+ (rc, rs) <- newChan
169+ agentPid <- mxAgent (MxAgentId "lifecycle-listener-agent") initState [
170+ (mxSink $ \ev -> do
171+ st <- mxGetLocal
172+ let act =
173+ case ev of
174+ (MxSpawned _) -> mxSetLocal (ev:st)
175+ (MxProcessDied _ _) -> mxSetLocal (ev:st)
176+ _ -> return ()
177+ act >> (liftMX $ sendChan rc ()) >> mxReady),
178+ (mxSink $ \(ev, sp :: SendPort Bool) -> do
179+ st <- mxGetLocal
180+ let found =
181+ case ev of
182+ MxSpawned p ->
183+ isJust $ find (\ev' ->
184+ case ev' of
185+ (MxSpawned p') -> p' == p
186+ _ -> False) st
187+ MxProcessDied p r ->
188+ isJust $ find (\ev' ->
189+ case ev' of
190+ (MxProcessDied p' r') -> p' == p && r == r'
191+ _ -> False) st
192+ _ -> False
193+ liftMX $ sendChan sp found
194+ mxReady)
195+ ]
196+
197+ -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
198+ faff 2000000 rs
199+
200+ (sp, rp) <- newChan :: Process (SendPort (), ReceivePort ())
201+
202+ pid <- spawnLocal $ expect >>= sendChan sp
203+
204+ -- By waiting for a monitor notification, we have a
205+ -- higher probably that the agent has seen the spawn and died events
206+ monitor pid
207+
208+ send pid ()
209+ rct <- receiveChanTimeout 10000000 rp
210+ unless (isJust rct) $ die "No response on channel"
211+
212+ pmn <- receiveTimeout 2000000 [ match (\ProcessMonitorNotification{} -> return ()) ]
213+ unless (isJust pmn) $ die "No monitor notification arrived"
214+
215+ -- TODO: yes, this is racy, but we're at the mercy of the scheduler here...
216+ faff 2000000 rs
217+
218+ (replyTo, reply) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
219+ mxNotify (MxSpawned pid, replyTo)
220+ mxNotify (MxProcessDied pid DiedNormal, replyTo)
221+
222+ seenAlive <- receiveChanTimeout 10000000 reply
223+ seenDead <- receiveChanTimeout 10000000 reply
224+
225+ stash result $ foldr (&&) True $ catMaybes [seenAlive, seenDead]
226+ kill timer "test-complete"
227+ kill agentPid "test-complete"
228+ where
229+ faff delay port = do
230+ res <- receiveChanTimeout delay port
231+ unless (isNothing res) $ pause delay
232+
233+testMxRegEvents :: TestResult () -> Process ()
234+testMxRegEvents result = do
235+ {- This test only deals with the local case, to ensure that we are being
236+ notified in the expected order - the remote cases related to the
237+ behaviour of the node controller are contained in the CH test suite. -}
238+ ensure (stash result ()) $ do
239+ let label = "testMxRegEvents"
240+ let agentLabel = "mxRegEvents-agent"
241+ let delay = 1000000
242+ (regChan, regSink) <- newChan
243+ (unRegChan, unRegSink) <- newChan
244+ agent <- mxAgent (MxAgentId agentLabel) () [
245+ mxSink $ \ev -> do
246+ case ev of
247+ MxRegistered pid label'
248+ | label' == label -> liftMX $ sendChan regChan (label', pid)
249+ MxUnRegistered pid label'
250+ | label' == label -> liftMX $ sendChan unRegChan (label', pid)
251+ _ -> return ()
252+ mxReady
253+ ]
254+
255+ p1 <- spawnLocal expect
256+ p2 <- spawnLocal expect
257+
258+ register label p1
259+ reg1 <- receiveChanTimeout delay regSink
260+ reg1 `shouldBe` equalTo (Just (label, p1))
261+
262+ unregister label
263+ unreg1 <- receiveChanTimeout delay unRegSink
264+ unreg1 `shouldBe` equalTo (Just (label, p1))
265+
266+ register label p2
267+ reg2 <- receiveChanTimeout delay regSink
268+ reg2 `shouldBe` equalTo (Just (label, p2))
269+
270+ reregister label p1
271+ unreg2 <- receiveChanTimeout delay unRegSink
272+ unreg2 `shouldBe` equalTo (Just (label, p2))
273+
274+ reg3 <- receiveChanTimeout delay regSink
275+ reg3 `shouldBe` equalTo (Just (label, p1))
276+
277+ mapM_ (flip kill $ "test-complete") [agent, p1, p2]
278+
279+testMxRegMon :: LocalNode -> TestResult () -> Process ()
280+testMxRegMon remoteNode result = do
281+ ensure (stash result ()) $ do
282+ -- ensure that when a registered process dies, we get a notification that
283+ -- it has been unregistered as well as seeing the name get removed
284+ let label1 = "aaaaa"
285+ let label2 = "bbbbb"
286+ let isValid l = l == label1 || l == label2
287+ let agentLabel = "mxRegMon-agent"
288+ let delay = 1000000
289+ (regChan, regSink) <- newChan
290+ (unRegChan, unRegSink) <- newChan
291+ agent <- mxAgent (MxAgentId agentLabel) () [
292+ mxSink $ \ev -> do
293+ case ev of
294+ MxRegistered pid label
295+ | isValid label -> liftMX $ sendChan regChan (label, pid)
296+ MxUnRegistered pid label
297+ | isValid label -> liftMX $ sendChan unRegChan (label, pid)
298+ _ -> return ()
299+ mxReady
300+ ]
301+
302+ (sp, rp) <- newChan
303+ liftIO $ forkProcess remoteNode $ do
304+ getSelfPid >>= sendChan sp
305+ expect :: Process ()
306+
307+ p1 <- receiveChan rp
308+
309+ register label1 p1
310+ reg1 <- receiveChanTimeout delay regSink
311+ reg1 `shouldBe` equalTo (Just (label1, p1))
312+
313+ register label2 p1
314+ reg2 <- receiveChanTimeout delay regSink
315+ reg2 `shouldBe` equalTo (Just (label2, p1))
316+
317+ n1 <- whereis label1
318+ n1 `shouldBe` equalTo (Just p1)
319+
320+ n2 <- whereis label2
321+ n2 `shouldBe` equalTo (Just p1)
322+
323+ kill p1 "goodbye"
324+
325+ unreg1 <- receiveChanTimeout delay unRegSink
326+ unreg2 <- receiveChanTimeout delay unRegSink
327+
328+ let evts = [unreg1, unreg2]
329+ -- we can't rely on the order of the values in the node controller's
330+ -- map (it's either racy to do so, or no such guarantee exists for Data.Map),
331+ -- so we simply verify that we received the un-registration events we expect
332+ evts `shouldContain` (Just (label1, p1))
333+ evts `shouldContain` (Just (label2, p1))
334+
335+ kill agent "test-complete"
336+
337+ensure :: Process () -> Process () -> Process ()
338+ensure = flip finally
339+
340+testNSend :: (String -> () -> Process ())
341+ -> Maybe LocalNode
342+ -> Process ()
343+testNSend op n = do
344+ us <- getSelfPid
345+ let delay = 5000000
346+ let label = "testMxSend" ++ (show us)
347+ let isValid = isValidLabel n label
348+
349+ testMxSend n label $ \p1 sink -> do
350+ register label p1
351+ reg1 <- receiveChanTimeout delay sink
352+ case reg1 of
353+ Just (MxRegistered pd lb)
354+ | pd == p1 && isValid lb -> return ()
355+ _ -> die $ "Reg-Failed: " ++ show reg1
356+
357+ op label ()
358+
359+ sent <- receiveChanTimeout delay sink
360+ case sent of
361+ Just (MxSentToName lb by _)
362+ | by == us && isValid lb -> return True
363+ _ -> die $ "Send-Failed: " ++ show sent
364+
365+ where
366+ isValidLabel nd l1 l2
367+ | l2 == l2 = True
368+ | isJust nd = l2 == l1 ++ "@" ++ show (localNodeId $ fromJust nd)
369+ | otherwise = False
370+
371+testSend :: (ProcessId -> () -> Process ())
372+ -> Maybe LocalNode
373+ -> Process ()
374+testSend op n = do
375+ us <- getSelfPid
376+ let label = "testMxSend" ++ (show us)
377+ testMxSend n label $ \p1 sink -> do
378+ -- initiate a send
379+ op p1 ()
380+
381+ -- verify the management event
382+ sent <- receiveChanTimeout 5000000 sink
383+ case sent of
384+ Just (MxSent pidTo pidFrom _)
385+ | pidTo == p1 && pidFrom == us -> return True
386+ _ -> return False
387+
388+testChan :: (SendPort () -> () -> Process ())
389+ -> Maybe LocalNode
390+ -> Process ()
391+testChan op n = testMxSend n "" $ \p1 sink -> do
392+
393+ us <- getSelfPid
394+ send p1 us
395+
396+ cleared <- receiveChanTimeout 2000000 sink
397+ case cleared of
398+ Just (MxSent pidTo pidFrom _)
399+ | pidTo == p1 && pidFrom == us -> return ()
400+ _ -> die "Received uncleared Mx Event"
401+
402+ chan <- receiveTimeout 5000000 [ match return ]
403+ let ch' = fromMaybe (error "No reply chan received") chan
404+
405+ -- initiate a send
406+ op ch' ()
407+
408+ -- verify the management event
409+ sent <- receiveChanTimeout 5000000 sink
410+ case sent of
411+ Just (MxSentToPort sId spId _)
412+ | sId == us && spId == sendPortId ch' -> return True
413+ _ -> return False
414+
415+type SendTest = ProcessId -> ReceivePort MxEvent -> Process Bool
416+
417+testMxSend :: Maybe LocalNode -> String -> SendTest -> Process ()
418+testMxSend mNode label test = do
419+ us <- getSelfPid
420+ (sp, rp) <- newChan
421+ (chan, sink) <- newChan
422+ agent <- mxAgent (MxAgentId $ agentLabel us) () [
423+ mxSink $ \ev -> do
424+ case ev of
425+ m@(MxSentToPort _ cid _)
426+ | cid /= sendPortId chan
427+ && cid /= sendPortId sp -> liftMX $ sendChan chan m
428+ m@(MxSent _ fromPid _)
429+ | fromPid == us -> liftMX $ sendChan chan m
430+ m@(MxSentToName _ fromPid _)
431+ | fromPid == us -> liftMX $ sendChan chan m
432+ m@(MxRegistered _ name)
433+ | name == label -> liftMX $ sendChan chan m
434+ _ -> return ()
435+ mxReady
436+ ]
437+
438+ case mNode of
439+ Nothing -> void $ spawnLocal (proc sp)
440+ Just remoteNode -> void $ liftIO $ forkProcess remoteNode $ proc sp
441+
442+ p1 <- receiveChanTimeout 2000000 rp
443+ unless (isJust p1) $ die "Timed out waiting for ProcessId"
444+ res <- try $ test (fromJust p1) sink
445+
446+ kill agent "bye"
447+ kill (fromJust p1) "bye"
448+
449+ case res of
450+ Left (ProcessExitException _ m) -> (liftIO $ putStrLn $ "SomeException-" ++ show m) >> die m
451+ Right tr -> tr `shouldBe` equalTo True
452+
453+
454+ where
455+ agentLabel s = "mx-unsafe-check-agent-" ++ show s
456+ proc sp' = getSelfPid >>= sendChan sp' >> go Nothing
457+
458+ go :: Maybe (ReceivePort ()) -> Process ()
459+ go Nothing = receiveTimeout 5000000 [ match replyChannel ] >>= go
460+ go c@(Just c') = receiveWait [ matchChan c' return ] >> go c
461+
462+ replyChannel p' = do
463+ (s, r) <- newChan
464+ send p' s
465+ return r
466+
467+tests :: TestTransport -> IO [Test]
468+tests TestTransport{..} = do
469+ node1 <- newLocalNode testTransport initRemoteTable
470+ node2 <- newLocalNode testTransport initRemoteTable
471+ return [
472+ testGroup "MxAgents" [
473+ testCase "EventHandling"
474+ (delayedAssertion
475+ "expected True, but events where not as expected"
476+ node1 True testAgentEventHandling)
477+ , testCase "InterAgentBroadcast"
478+ (delayedAssertion
479+ "expected (), but no broadcast was received"
480+ node1 (Just ()) testAgentBroadcast)
481+ , testCase "AgentMailboxHandling"
482+ (delayedAssertion
483+ "expected (Just ()), but no regular (mailbox) input was handled"
484+ node1 (Just ()) testAgentMailboxHandling)
485+ , testCase "AgentDualInputHandling"
486+ (delayedAssertion
487+ "expected sum = 15, but the result was Nothing"
488+ node1 (Just 15 :: Maybe Int) testAgentDualInput)
489+ , testCase "AgentInputPrioritisation"
490+ (delayedAssertion
491+ "expected [first, second, third, fourth, fifth], but result diverged"
492+ node1 (sort ["first", "second",
493+ "third", "fourth",
494+ "fifth"]) testAgentPrioritisation)
495+ ]
496+ , testGroup "MxEvents" [
497+ testCase "NameRegistrationEvents"
498+ (delayedAssertion
499+ "expected registration events to map to the correct ProcessId"
500+ node1 () testMxRegEvents)
501+ , testCase "PostDeathNameUnRegistrationEvents"
502+ (delayedAssertion
503+ "expected process deaths to result in unregistration events"
504+ node1 () (testMxRegMon node2))
505+ , testGroup "SendEvents" $ buildTestCases node1 node2
506+ ]
507+ ]
508+ where
509+ buildTestCases n1 n2 = let nid = localNodeId n2 in build n1 n2 [
510+ ("NSend", [
511+ ("nsend", testNSend nsend)
512+ , ("Unsafe.nsend", testNSend Unsafe.nsend)
513+ , ("nsendRemote", testNSend (nsendRemote nid))
514+ , ("Unsafe.nsendRemote", testNSend (Unsafe.nsendRemote nid))
515+ ])
516+ , ("Send", [
517+ ("send", testSend send)
518+ , ("Unsafe.send", testSend Unsafe.send)
519+ , ("usend", testSend usend)
520+ , ("Unsafe.usend", testSend Unsafe.usend)
521+ , ("sendChan", testChan sendChan)
522+ , ("Unsafe.sendChan", testChan Unsafe.sendChan)
523+ ])
524+ , ("Forward", [
525+ ("forward", testSend (\p m -> forward (unsafeCreateUnencodedMessage m) p))
526+ , ("uforward", testSend (\p m -> uforward (unsafeCreateUnencodedMessage m) p))
527+ ])
528+ ]
529+
530+ build :: LocalNode
531+ -> LocalNode
532+ -> [(String, [(String, (Maybe LocalNode -> Process ()))])]
533+ -> [Test]
534+ build n ln specs =
535+ [ testGroup (intercalate "-" [groupName, caseSuffix]) [
536+ testCase (intercalate "-" [caseName, caseSuffix])
537+ (runProcess n (caseImpl caseNode))
538+ | (caseName, caseImpl) <- groupCases
539+ ]
540+ | (groupName, groupCases) <- specs
541+ , (caseSuffix, caseNode) <- [("RemotePid", Just ln), ("LocalPid", Nothing)]
542+ ]
tests/TestReceive.hs → distributed-process-tests/src/Control/Distributed/Process/Tests/Receive.hsrenamed+22−36
@@ -1,24 +1,21 @@
1−{-# LANGUAGE TemplateHaskell, DeriveDataTypeable #-}
1+{-# LANGUAGE TemplateHaskell #-}
22{-# OPTIONS_GHC -Wall #-}
3−import Control.Distributed.Process
4−import Control.Distributed.Process.Closure
5−import Control.Distributed.Process.Node
3+
4+-- | XXX test doesn't work, because failure exceptions don't get propagated. The
5+-- test always claims to succeed, even if it failed.
6+
7+module Control.Distributed.Process.Tests.Receive (tests) where
8+
9+import Network.Transport.Test (TestTransport(..))
610
711import Network.Transport (Transport)
8−import Network.Transport.TCP
9− ( createTransportExposeInternals
10− , defaultTCPParameters
11− , TransportInternals(socketBetween)
12− )
12+import Control.Distributed.Process
13+import Control.Distributed.Process.Node
1314
1415import Control.Monad
15−import Text.Printf
16−import Data.DeriveTH
17−import Data.Binary
18−import Data.Typeable
1916
2017import Test.HUnit (Assertion, (@?=))
21−import Test.Framework (Test, defaultMain)
18+import Test.Framework (Test)
2219import Test.Framework.Providers.HUnit (testCase)
2320
2421-- Tests:
@@ -45,7 +42,7 @@ recTest2 :: ReceivePort ()
4542 -> SendPort String
4643 -> ReceivePort String -> ReceivePort String
4744 -> Process ()
48−recTest2 wait sync r1 r2 = do
45+recTest2 wait sync r1 _ = do
4946 forever $ do
5047 receiveChan wait
5148 r <- receiveWait
@@ -58,7 +55,7 @@ recTest3 :: ReceivePort ()
5855 -> SendPort String
5956 -> ReceivePort String -> ReceivePort String
6057 -> Process ()
61−recTest3 wait sync r1 r2 = do
58+recTest3 wait sync r1 _ = do
6259 forever $ do
6360 receiveChan wait
6461 r <- receiveWait
@@ -71,7 +68,7 @@ recTest4 :: ReceivePort ()
7168 -> SendPort String
7269 -> ReceivePort String -> ReceivePort String
7370 -> Process ()
74−recTest4 wait sync r1 r2 = do
71+recTest4 wait sync r1 _ = do
7572 forever $ do
7673 receiveChan wait
7774 r <- receiveWait
@@ -81,17 +78,15 @@ recTest4 wait sync r1 r2 = do
8178 ]
8279 sendChan sync r
8380
84−
8581master :: Process ()
8682master = do
87−
8883 (waits,waitr) <- newChan
8984 (syncs,syncr) <- newChan
90− let go expect = do
85+ let go expected = do
9186 sendChan waits ()
9287 r <- receiveChan syncr
93− liftIO $ print (r,expect, r == expect)
94− liftIO $ r @?= expect
88+ liftIO $ print (r, expected, r == expected)
89+ liftIO $ r @?= expected
9590
9691 liftIO $ putStrLn "---- Test 1 ----"
9792 (s1,r1) <- newChan
@@ -147,22 +142,13 @@ master = do
147142
148143 terminate
149144
150−
151145testReceive :: Transport -> RemoteTable -> Assertion
152146testReceive transport rtable = do
153147 node <- newLocalNode transport rtable
154148 runProcess node $ master
155149
156−tests :: (Transport, TransportInternals) -> RemoteTable -> [Test]
157−tests (transport, transportInternals) rtable = [
158− testCase "testReceive" (testReceive transport rtable)
159− ]
160−
161−-- NB. test doesn't work, because failure exceptions don't get
162−-- propagated. The test always claims to succeed, even if it failed.
163−
164−main :: IO ()
165−main = do
166− Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
167− let rtable = initRemoteTable
168− defaultMain (tests transport rtable)
150+tests :: TestTransport -> IO [Test]
151+tests TestTransport{..} = do
152+ let rtable = initRemoteTable
153+ return
154+ [ testCase "testReceive" (testReceive testTransport rtable) ]
tests/TestStats.hs → distributed-process-tests/src/Control/Distributed/Process/Tests/Stats.hsrenamed+31−64
@@ -1,6 +1,9 @@
11{-# LANGUAGE DeriveDataTypeable #-}
22{-# OPTIONS_GHC -fno-warn-orphans #-}
3−module Main where
3+module Control.Distributed.Process.Tests.Stats (tests) where
4+
5+import Control.Distributed.Process.Tests.Internal.Utils
6+import Network.Transport.Test (TestTransport(..))
47
58import Control.Concurrent.MVar
69 ( MVar
@@ -10,50 +13,23 @@ import Control.Concurrent.MVar
1013 )
1114import Control.Distributed.Process
1215import Control.Distributed.Process.Node
13− ( forkProcess
14− , newLocalNode
15− , initRemoteTable
16− , closeLocalNode
17− , LocalNode)
18−import Data.Binary()
19−import Data.Typeable()
20−import Network.Transport.TCP
21−import Prelude hiding (catch, log)
16+import Data.Binary ()
17+import Data.Typeable ()
18+
19+#if ! MIN_VERSION_base(4,6,0)
20+import Prelude hiding (catch)
21+#endif
22+
2223import Test.Framework
2324 ( Test
24− , defaultMain
2525 , testGroup
2626 )
2727import Test.HUnit (Assertion)
28−import Test.HUnit.Base (assertBool)
2928import Test.Framework.Providers.HUnit (testCase)
3029
31−-- these utilities have been cribbed from distributed-process-platform
32−-- we should really find a way to share them...
33−
34−-- | A mutable cell containing a test result.
35−type TestResult a = MVar a
36−
37−delayedAssertion :: (Eq a) => String -> LocalNode -> a ->
38− (TestResult a -> Process ()) -> Assertion
39−delayedAssertion note localNode expected testProc = do
40− result <- newEmptyMVar
41− _ <- forkProcess localNode $ testProc result
42− assertComplete note result expected
43−
44−assertComplete :: (Eq a) => String -> MVar a -> a -> IO ()
45−assertComplete msg mv a = do
46− b <- takeMVar mv
47− assertBool msg (a == b)
48−
49−stash :: TestResult a -> a -> Process ()
50−stash mvar x = liftIO $ putMVar mvar x
51−
52−------
53−
5430testLocalDeadProcessInfo :: TestResult (Maybe ProcessInfo) -> Process ()
5531testLocalDeadProcessInfo result = do
56− pid <- spawnLocal $ do "finish" <- expect; return ()
32+ pid <- spawnLocal $ do (_ :: String) <- expect; return ()
5733 mref <- monitor pid
5834 send pid "finish"
5935 _ <- receiveWait [
@@ -76,8 +52,10 @@ testLocalLiveProcessInfo result = do
7652 link self
7753 mRef <- monitor self
7854 stash mon mRef
79− "die" <- expect
80− return ()
55+ res <- expect
56+ case res of
57+ "die" -> return ()
58+ _ -> die $ "unexpected message received: " ++ res
8159
8260 monRef <- liftIO $ takeMVar mon
8361
@@ -97,8 +75,8 @@ testLocalLiveProcessInfo result = do
9775-- infoMessageQueueLength pInfo == Just 4 &&
9876 infoRegisteredNames pInfo == ["foobar"]
9977
100−testRemoteLiveProcessInfo :: LocalNode -> Assertion
101−testRemoteLiveProcessInfo node1 = do
78+testRemoteLiveProcessInfo :: TestTransport -> LocalNode -> Assertion
79+testRemoteLiveProcessInfo TestTransport{..} node1 = do
10280 serverAddr <- liftIO $ newEmptyMVar :: IO (MVar ProcessId)
10381 liftIO $ launchRemote serverAddr
10482 serverPid <- liftIO $ takeMVar serverAddr
@@ -108,21 +86,23 @@ testRemoteLiveProcessInfo node1 = do
10886 -- our send op shouldn't overtake link or monitor requests AFAICT
10987 -- so a little table tennis should get us synchronised properly
11088 send serverPid (self, "ping")
111− "pong" <- expect
89+ pong <- expect
11290 pInfo <- getProcessInfo serverPid
113− stash result $ pInfo /= Nothing
114− where
91+ stash result $ pong == "pong" && pInfo /= Nothing
92+ where
11593 launchRemote :: MVar ProcessId -> IO ()
11694 launchRemote locMV = do
117− node2 <- liftIO $ mkNode "8082"
95+ node2 <- liftIO $ newLocalNode testTransport initRemoteTable
11896 _ <- liftIO $ forkProcess node2 $ do
11997 self <- getSelfPid
12098 liftIO $ putMVar locMV self
12199 _ <- receiveWait [
122− match (\(pid, "ping") -> send pid "pong")
100+ match (\(pid, "ping") -> send pid "pong")
123101 ]
124− "stop" <- expect
125− return ()
102+ res <- expect
103+ case res of
104+ "stop" -> return ()
105+ _ -> die $ "unexpected message received: " ++ res
126106 return ()
127107
128108 withActiveRemote :: LocalNode
@@ -131,8 +111,9 @@ testRemoteLiveProcessInfo node1 = do
131111 a <- delayedAssertion "getProcessInfo remotePid failed" n True
132112 return a
133113
134−tests :: LocalNode -> IO [Test]
135−tests node1 = do
114+tests :: TestTransport -> IO [Test]
115+tests testtrans@TestTransport{..} = do
116+ node1 <- newLocalNode testTransport initRemoteTable
136117 return [
137118 testGroup "Process Info" [
138119 testCase "testLocalDeadProcessInfo"
@@ -144,19 +125,5 @@ tests node1 = do
144125 "expected process-info to be correctly populated"
145126 node1 True testLocalLiveProcessInfo)
146127 , testCase "testRemoveLiveProcessInfo"
147− (testRemoteLiveProcessInfo node1)
128+ (testRemoteLiveProcessInfo testtrans node1)
148129 ] ]
149−
150−mkNode :: String -> IO LocalNode
151−mkNode port = do
152− Right (transport1, _) <- createTransportExposeInternals
153− "127.0.0.1" port defaultTCPParameters
154− newLocalNode transport1 initRemoteTable
155−
156−main :: IO ()
157−main = do
158− node1 <- mkNode "8081"
159− testData <- tests node1
160− defaultMain testData
161− closeLocalNode node1
162− return ()
distributed-process-tests/src/Control/Distributed/Process/Tests/Tracing.hsnew file+431
@@ -0,0 +1,431 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# OPTIONS_GHC -fno-warn-orphans #-}
3+module Control.Distributed.Process.Tests.Tracing (tests) where
4+
5+import Control.Distributed.Process.Tests.Internal.Utils
6+import Network.Transport.Test (TestTransport(..))
7+
8+import Control.Applicative ((<*))
9+import Control.Concurrent (threadDelay)
10+import Control.Concurrent.MVar
11+ ( MVar
12+ , newEmptyMVar
13+ , newMVar
14+ , putMVar
15+ , takeMVar
16+ )
17+import Control.Distributed.Process
18+import Control.Distributed.Process.Node
19+import Control.Distributed.Process.Debug
20+import Control.Distributed.Process.Management
21+ ( MxEvent(..)
22+ )
23+import qualified Control.Exception as IO (bracket)
24+import Data.List (isPrefixOf, isSuffixOf)
25+
26+#if ! MIN_VERSION_base(4,6,0)
27+import Prelude hiding (catch, log)
28+#else
29+import Prelude hiding ((<*))
30+#endif
31+
32+import Test.Framework
33+ ( Test
34+ , testGroup
35+ )
36+import Test.Framework.Providers.HUnit (testCase)
37+import System.Environment (getEnvironment)
38+-- These are available in System.Environment only since base 4.7
39+import System.SetEnv (setEnv, unsetEnv)
40+
41+
42+testSpawnTracing :: TestResult Bool -> Process ()
43+testSpawnTracing result = do
44+ setTraceFlags defaultTraceFlags {
45+ traceSpawned = (Just TraceAll)
46+ , traceDied = (Just TraceAll)
47+ }
48+
49+ evSpawned <- liftIO $ newEmptyMVar
50+ evDied <- liftIO $ newEmptyMVar
51+ tracer <- startTracer $ \ev -> do
52+ case ev of
53+ (MxSpawned p) -> liftIO $ putMVar evSpawned p
54+ (MxProcessDied p r) -> liftIO $ putMVar evDied (p, r)
55+ _ -> return ()
56+
57+ (sp, rp) <- newChan
58+ pid <- spawnLocal $ sendChan sp ()
59+ () <- receiveChan rp
60+
61+ tracedAlive <- liftIO $ takeMVar evSpawned
62+ (tracedDead, tracedReason) <- liftIO $ takeMVar evDied
63+
64+ mref <- monitor tracer
65+ stopTracer -- this is asynchronous, so we need to wait...
66+ receiveWait [
67+ matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mref)
68+ ((\_ -> return ()))
69+ ]
70+ setTraceFlags defaultTraceFlags
71+ stash result (tracedAlive == pid &&
72+ tracedDead == pid &&
73+ tracedReason == DiedNormal)
74+
75+testTraceRecvExplicitPid :: TestResult Bool -> Process ()
76+testTraceRecvExplicitPid result = do
77+ res <- liftIO $ newEmptyMVar
78+ pid <- spawnLocal $ do
79+ self <- getSelfPid
80+ expect >>= (flip sendChan) self
81+ withFlags defaultTraceFlags {
82+ traceRecv = traceOnly [pid]
83+ } $ do
84+ withTracer
85+ (\ev ->
86+ case ev of
87+ (MxReceived pid' _) -> stash res (pid == pid')
88+ _ -> return ()) $ do
89+ (sp, rp) <- newChan
90+ send pid sp
91+ p <- receiveChan rp
92+ res' <- liftIO $ takeMVar res
93+ stash result (res' && (p == pid))
94+ return ()
95+
96+testTraceRecvNamedPid :: TestResult Bool -> Process ()
97+testTraceRecvNamedPid result = do
98+ res <- liftIO $ newEmptyMVar
99+ pid <- spawnLocal $ do
100+ self <- getSelfPid
101+ register "foobar" self
102+ expect >>= (flip sendChan) self
103+ withFlags defaultTraceFlags {
104+ traceRecv = traceOnly ["foobar"]
105+ } $ do
106+ withTracer
107+ (\ev ->
108+ case ev of
109+ (MxReceived pid' _) -> stash res (pid == pid')
110+ _ -> return ()) $ do
111+ (sp, rp) <- newChan
112+ send pid sp
113+ p <- receiveChan rp
114+ res' <- liftIO $ takeMVar res
115+ stash result (res' && (p == pid))
116+ return ()
117+
118+testTraceSending :: TestResult Bool -> Process ()
119+testTraceSending result = do
120+ pid <- spawnLocal $ (expect :: Process String) >> return ()
121+ self <- getSelfPid
122+ res <- liftIO $ newEmptyMVar
123+ withFlags defaultTraceFlags { traceSend = traceOn } $ do
124+ withTracer
125+ (\ev ->
126+ case ev of
127+ (MxSent to from msg) -> do
128+ mS <- unwrapMessage msg :: Process (Maybe String)
129+ case mS of
130+ (Just s) -> do stash res (to == pid && from == self && s == "hello there")
131+ stash res (to == pid && from == self)
132+ _ -> die "failed state invariant, message type unmatched..."
133+ _ ->
134+ return ()) $ do
135+ send pid "hello there"
136+ res' <- liftIO $ takeMVar res
137+ stash result res'
138+
139+testTraceRegistration :: TestResult Bool -> Process ()
140+testTraceRegistration result = do
141+ (sp, rp) <- newChan
142+ pid <- spawnLocal $ do
143+ self <- getSelfPid
144+ () <- expect
145+ register "foobar" self
146+ sendChan sp ()
147+ () <- expect
148+ return ()
149+ res <- liftIO $ newEmptyMVar
150+ withFlags defaultTraceFlags { traceRegistered = traceOn } $ do
151+ withTracer
152+ (\ev ->
153+ case ev of
154+ MxRegistered p s ->
155+ stash res (p == pid && s == "foobar")
156+ _ ->
157+ return ()) $ do
158+ _ <- monitor pid
159+ send pid ()
160+ () <- receiveChan rp
161+ send pid ()
162+ receiveWait [
163+ match (\(ProcessMonitorNotification _ _ _) -> return ())
164+ ]
165+ res' <- liftIO $ takeMVar res
166+ stash result res'
167+
168+testTraceUnRegistration :: TestResult Bool -> Process ()
169+testTraceUnRegistration result = do
170+ pid <- spawnLocal $ do
171+ () <- expect
172+ unregister "foobar"
173+ () <- expect
174+ return ()
175+ register "foobar" pid
176+ res <- liftIO $ newEmptyMVar
177+ withFlags defaultTraceFlags { traceUnregistered = traceOn } $ do
178+ withTracer
179+ (\ev ->
180+ case ev of
181+ MxUnRegistered p n -> do
182+ stash res (p == pid && n == "foobar")
183+ send pid ()
184+ _ ->
185+ return ()) $ do
186+ mref <- monitor pid
187+ send pid ()
188+ receiveWait [
189+ matchIf (\(ProcessMonitorNotification mref' _ _) -> mref == mref')
190+ (\_ -> return ())
191+ ]
192+ res' <- liftIO $ takeMVar res
193+ stash result res'
194+
195+testTraceLayering :: TestResult () -> Process ()
196+testTraceLayering result = do
197+ pid <- spawnLocal $ do
198+ getSelfPid >>= register "foobar"
199+ () <- expect
200+ traceMessage ("traceMsg", 123 :: Int)
201+ return ()
202+ withFlags defaultTraceFlags {
203+ traceDied = traceOnly [pid]
204+ , traceRecv = traceOnly ["foobar"]
205+ } $ doTest pid result
206+ return ()
207+ where
208+ doTest :: ProcessId -> MVar () -> Process ()
209+ doTest pid result' = do
210+ -- TODO: this is pretty gross, even for a test case
211+ died <- liftIO $ newEmptyMVar
212+ withTracer
213+ (\ev ->
214+ case ev of
215+ MxProcessDied _ _ -> liftIO $ putMVar died ()
216+ _ -> return ())
217+ ( do {
218+ recv <- liftIO $ newEmptyMVar
219+ ; withTracer
220+ (\ev' ->
221+ case ev' of
222+ MxReceived _ _ -> liftIO $ putMVar recv ()
223+ _ -> return ())
224+ ( do {
225+ user <- liftIO $ newEmptyMVar
226+ ; withTracer
227+ (\ev'' ->
228+ case ev'' of
229+ MxUser _ -> liftIO $ putMVar user ()
230+ _ -> return ())
231+ (send pid () >> (liftIO $ takeMVar user))
232+ ; liftIO $ takeMVar recv
233+ })
234+ ; liftIO $ takeMVar died
235+ })
236+ liftIO $ putMVar result' ()
237+
238+testRemoteTraceRelay :: TestTransport -> TestResult Bool -> Process ()
239+testRemoteTraceRelay TestTransport{..} result =
240+ let flags = defaultTraceFlags { traceSpawned = traceOn }
241+ in do
242+ node2 <- liftIO $ newLocalNode testTransport initRemoteTable
243+ mvNid <- liftIO $ newEmptyMVar
244+
245+ -- As well as needing node2's NodeId, we want to
246+ -- redirect all its logs back here, to avoid generating
247+ -- garbage on stderr for the duration of the test run.
248+ -- Here we set up that relay, and then wait for a signal
249+ -- that the tracer (on node1) has seen the expected
250+ -- MxSpawned message, at which point we're finished
251+
252+ pid <- splinchLogger node2 mvNid
253+ nid <- liftIO $ takeMVar mvNid
254+ mref <- monitor pid
255+ observedPid <- liftIO $ newEmptyMVar
256+ spawnedPid <- liftIO $ newEmptyMVar
257+ setTraceFlagsRemote flags nid
258+
259+ withFlags defaultTraceFlags { traceSpawned = traceOn } $ do
260+ withTracer
261+ (\ev ->
262+ case ev of
263+ MxSpawned p -> stash observedPid p >> send pid ()
264+ _ -> return ()) $ do
265+ relayPid <- startTraceRelay nid
266+ liftIO $ threadDelay 1000000
267+ p <- liftIO $ forkProcess node2 $ do
268+ expectTimeout 1000000 :: Process (Maybe ())
269+ return ()
270+ stash spawnedPid p
271+
272+ -- Now we wait for (the outer) pid to exit. This won't happen until
273+ -- our tracer has seen the trace event for `p' and sent `p' the
274+ -- message it's waiting for prior to exiting
275+ receiveWait [
276+ matchIf (\(ProcessMonitorNotification mref' _ _) -> mref == mref')
277+ (\_ -> return ())
278+ ]
279+
280+ relayRef <- monitor relayPid
281+ kill relayPid "stop"
282+ receiveWait [
283+ matchIf (\(ProcessMonitorNotification rref' _ _) -> rref' == relayRef)
284+ (\_ -> return ())
285+ ]
286+ observed <- liftIO $ takeMVar observedPid
287+ expected <- liftIO $ takeMVar spawnedPid
288+ stash result (observed == expected)
289+ -- and just to be polite...
290+ liftIO $ closeLocalNode node2
291+
292+ where
293+ splinchLogger n2 mv = do
294+ mLog <- whereis "logger"
295+ case mLog of
296+ Nothing -> die "no logger registered"
297+ Just log' -> do liftIO $ forkProcess n2 $ do
298+ logRelay <- spawnLocal $ relay log'
299+ reregister "logger" logRelay
300+ getSelfNode >>= stash mv >> (expect :: Process ())
301+
302+
303+-- | Sets the value of an environment variable while executing the given IO
304+-- computation and restores the preceeding value upon completion.
305+withEnv :: String -> String -> IO a -> IO a
306+withEnv var val =
307+ IO.bracket (fmap (lookup var) getEnvironment <* setEnv var val)
308+ (maybe (unsetEnv var) (setEnv var))
309+ . const
310+
311+-- | Tests that one and only one interesting trace message is produced when a
312+-- given action is performed. A message is considered interesting when the given
313+-- function return @True@.
314+testSystemLoggerMsg :: TestTransport
315+ -> Process a
316+ -> (a -> String -> Bool)
317+ -> IO ()
318+testSystemLoggerMsg t action interestingMessage =
319+ withEnv "DISTRIBUTED_PROCESS_TRACE_CONSOLE" "yes" $
320+ withEnv "DISTRIBUTED_PROCESS_TRACE_FLAGS" "pdnusrl" $ do
321+ n <- newLocalNode (testTransport t) initRemoteTable
322+
323+ runProcess n $ do
324+ self <- getSelfPid
325+ reregister "trace.logger" self
326+ a <- action
327+ let interestingMessage' (_ :: String, msg) = interestingMessage a msg
328+ -- Wait for the trace message.
329+ receiveWait [ matchIf interestingMessage' $ const $ return () ]
330+ -- Only one interesting message should arrive.
331+ expectedTimeout <- receiveTimeout
332+ 100000
333+ [ matchIf interestingMessage' $ const $ return () ]
334+ case expectedTimeout of
335+ Nothing -> return ()
336+ Just _ -> die "Unexpected message arrived..."
337+
338+
339+-- | Tests that one and only one trace message is produced when a message is
340+-- received.
341+testSystemLoggerMxReceive :: TestTransport -> IO ()
342+testSystemLoggerMxReceive t = testSystemLoggerMsg t
343+ (getSelfPid >>= flip send ())
344+ (\_ msg -> "MxReceived" `isPrefixOf` msg
345+ -- discard traces of internal messages
346+ && not (":: RegisterReply" `isSuffixOf` msg)
347+ )
348+
349+-- | Tests that one and only one trace message is produced when a message is
350+-- sent.
351+testSystemLoggerMxSent :: TestTransport -> IO ()
352+testSystemLoggerMxSent t = testSystemLoggerMsg t
353+ (getSelfPid >>= flip send ())
354+ (const $ isPrefixOf "MxSent")
355+
356+-- | Tests that one and only one trace message is produced when a process dies.
357+testSystemLoggerMxProcessDied :: TestTransport -> IO ()
358+testSystemLoggerMxProcessDied t = testSystemLoggerMsg t
359+ (spawnLocal $ return ())
360+ (\pid -> isPrefixOf $ "MxProcessDied " ++ show pid)
361+
362+-- | Tests that one and only one trace message appears when a process spawns.
363+testSystemLoggerMxSpawned :: TestTransport -> IO ()
364+testSystemLoggerMxSpawned t = testSystemLoggerMsg t
365+ (spawnLocal $ return ())
366+ (\pid -> isPrefixOf $ "MxSpawned " ++ show pid)
367+
368+-- | Tests that one and only one trace message appears when a process is
369+-- registered.
370+testSystemLoggerMxRegistered :: TestTransport -> IO ()
371+testSystemLoggerMxRegistered t = testSystemLoggerMsg t
372+ (getSelfPid >>= register "a" >> getSelfPid)
373+ (\self -> isPrefixOf $ "MxRegistered " ++ show self ++ " " ++ show "a")
374+
375+-- | Tests that one and only one trace message appears when a process is
376+-- unregistered.
377+testSystemLoggerMxUnRegistered :: TestTransport -> IO ()
378+testSystemLoggerMxUnRegistered t = testSystemLoggerMsg t
379+ (getSelfPid >>= register "a" >> unregister "a" >> getSelfPid)
380+ (\self -> isPrefixOf $ "MxUnRegistered " ++ show self ++ " " ++ show "a")
381+
382+tests :: TestTransport -> IO [Test]
383+tests testtrans@TestTransport{..} = do
384+ node1 <- newLocalNode testTransport initRemoteTable
385+ -- if we execute the test cases in parallel, the
386+ -- various tracers will race with one another and
387+ -- we'll get garbage results (or worse, deadlocks)
388+ lock <- liftIO $ newMVar ()
389+ return [
390+ testGroup "Tracing" [
391+ testCase "Spawn Tracing"
392+ (synchronisedAssertion
393+ "expected dead process-info to be ProcessInfoNone"
394+ node1 True testSpawnTracing lock)
395+ , testCase "Recv Tracing (Explicit Pid)"
396+ (synchronisedAssertion
397+ "expected a recv trace for the supplied pid"
398+ node1 True testTraceRecvExplicitPid lock)
399+ , testCase "Recv Tracing (Named Pid)"
400+ (synchronisedAssertion
401+ "expected a recv trace for the process registered as 'foobar'"
402+ node1 True testTraceRecvNamedPid lock)
403+ , testCase "Trace Send(er)"
404+ (synchronisedAssertion
405+ "expected a 'send' trace with the requisite fields set"
406+ node1 True testTraceSending lock)
407+ , testCase "Trace Registration"
408+ (synchronisedAssertion
409+ "expected a 'registered' trace"
410+ node1 True testTraceRegistration lock)
411+ , testCase "Trace Unregistration"
412+ (synchronisedAssertion
413+ "expected an 'unregistered' trace"
414+ node1 True testTraceUnRegistration lock)
415+ , testCase "Trace Layering"
416+ (synchronisedAssertion
417+ "expected blah"
418+ node1 () testTraceLayering lock)
419+ , testCase "Remote Trace Relay"
420+ (synchronisedAssertion
421+ "expected blah"
422+ node1 True (testRemoteTraceRelay testtrans) lock)
423+ , testGroup "SystemLoggerTracer"
424+ [ testCase "MxReceive" $ testSystemLoggerMxReceive testtrans
425+ , testCase "MxSent" $ testSystemLoggerMxSent testtrans
426+ , testCase "MxProcessDied" $ testSystemLoggerMxProcessDied testtrans
427+ , testCase "MxSpawned" $ testSystemLoggerMxSpawned testtrans
428+ , testCase "MxRegistered" $ testSystemLoggerMxRegistered testtrans
429+ , testCase "MxUnRegistered" $ testSystemLoggerMxUnRegistered testtrans
430+ ]
431+ ] ]
distributed-process-tests/src/Network/Transport/Test.hsnew file+12
@@ -0,0 +1,12 @@
1+module Network.Transport.Test where
2+
3+import qualified Network.Transport as NT
4+import Data.Typeable (Typeable)
5+
6+-- | Extra operations required of transports for the purposes of testing.
7+data TestTransport = TestTransport
8+ { -- | The transport to use for testing.
9+ testTransport :: NT.Transport
10+ -- | IO action to perform to simulate losing a connection.
11+ , testBreakConnection :: NT.EndPointAddress -> NT.EndPointAddress -> IO ()
12+ } deriving (Typeable)
distributed-process-tests/tests/runInMemory.hsnew file+30
@@ -0,0 +1,30 @@
1+-- Run tests using the TCP transport.
2+
3+module Main where
4+
5+import TEST_SUITE_MODULE (tests)
6+
7+import Network.Transport.Test (TestTransport(..))
8+import Network.Transport.InMemory
9+import Test.Framework (defaultMainWithArgs)
10+
11+import System.Environment (getArgs)
12+
13+main :: IO ()
14+main = do
15+ (transport, internals) <- createTransportExposeInternals
16+ ts <- tests TestTransport
17+ { testTransport = transport
18+ , testBreakConnection = \addr1 addr2 -> breakConnection internals addr1 addr2 "user error"
19+ }
20+ args <- getArgs
21+ -- Tests are time sensitive. Running the tests concurrently can slow them
22+ -- down enough that threads using threadDelay would wake up later than
23+ -- expected, thus changing the order in which messages were expected.
24+ -- Therefore we run the tests sequentially by passing "-j 1" to
25+ -- test-framework. This does not solve the issue but makes it less likely.
26+ --
27+ -- The problem was first detected with
28+ -- 'Control.Distributed.Process.Tests.CH.testMergeChannels'
29+ -- in particular.
30+ defaultMainWithArgs ts ("-j" : "1" : args)
distributed-process-tests/tests/runTCP.hsnew file+47
@@ -0,0 +1,47 @@
1+-- Run tests using the TCP transport.
2+--
3+module Main where
4+
5+import TEST_SUITE_MODULE (tests)
6+
7+import Network.Transport.Test (TestTransport(..))
8+import Network.Socket (sClose)
9+import Network.Transport.TCP
10+ ( createTransportExposeInternals
11+ , TransportInternals(socketBetween)
12+ , defaultTCPParameters
13+ , defaultTCPAddr
14+ , TCPParameters(..)
15+ )
16+import Test.Framework (defaultMainWithArgs)
17+
18+import Control.Concurrent (threadDelay)
19+import Control.Exception (IOException, try)
20+import System.Environment (getArgs)
21+import System.IO
22+
23+main :: IO ()
24+main = do
25+ hSetBuffering stdout LineBuffering
26+ hSetBuffering stderr LineBuffering
27+ Right (transport, internals) <-
28+ createTransportExposeInternals (defaultTCPAddr "127.0.0.1" "8080")
29+ defaultTCPParameters { transportConnectTimeout = Just 3000000 }
30+ ts <- tests TestTransport
31+ { testTransport = transport
32+ , testBreakConnection = \addr1 addr2 -> do
33+ esock <- try $ socketBetween internals addr1 addr2
34+ either (\e -> const (return ()) (e :: IOException)) sClose esock
35+ threadDelay 10000
36+ }
37+ args <- getArgs
38+ -- Tests are time sensitive. Running the tests concurrently can slow them
39+ -- down enough that threads using threadDelay would wake up later than
40+ -- expected, thus changing the order in which messages were expected.
41+ -- Therefore we run the tests sequentially by passing "-j 1" to
42+ -- test-framework. This does not solve the issue but makes it less likely.
43+ --
44+ -- The problem was first detected with
45+ -- 'Control.Distributed.Process.Tests.CH.testMergeChannels'
46+ -- in particular.
47+ defaultMainWithArgs ts ("-j" : "1" : args)
distributed-process.cabal+120−178
@@ -1,15 +1,15 @@
11Name: distributed-process
2−Version: 0.4.2
2+Version: 0.7.4
33Cabal-Version: >=1.8
44Build-Type: Simple
55License: BSD3
66License-File: LICENSE
7−Copyright: Well-Typed LLP
7+Copyright: Well-Typed LLP, Tweag I/O Limited
88Author: Duncan Coutts, Nicolas Wu, Edsko de Vries
9−Maintainer: watson.timothy@gmail.com, edsko@well-typed.com, duncan@well-typed.com
9+Maintainer: Tim Watson <watson.timothy@gmail.com>
1010Stability: experimental
1111Homepage: http://haskell-distributed.github.com/
12−Bug-Reports: http://cloud-haskell.atlassian.net
12+Bug-Reports: https://github.com/haskell-distributed/distributed-process/issues
1313Synopsis: Cloud Haskell: Erlang-style concurrency in Haskell
1414Description: This is an implementation of Cloud Haskell, as described in
1515 /Towards Haskell in the Cloud/ by Jeff Epstein, Andrew Black,
@@ -17,12 +17,13 @@ Description: This is an implementation of Cloud Haskell, as described in
1717 (<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),
1818 although some of the details are different. The precise message
1919 passing semantics are based on /A unified semantics for future Erlang/
20− by Hans Svensson, Lars-Åke Fredlund and Clara Benac Earle.
20+ by Hans Svensson, Lars-&#xc5;ke Fredlund and Clara Benac Earle.
2121
2222 You will probably also want to install a Cloud Haskell backend such
2323 as distributed-process-simplelocalnet.
24−Tested-With: GHC==7.2.2 GHC==7.4.1 GHC==7.4.2
24+Tested-With: GHC==7.10.3 GHC==8.0.2 GHC==8.2.2 GHC==8.4.4
2525Category: Control
26+extra-source-files: ChangeLog
2627
2728Source-Repository head
2829 Type: git
@@ -33,194 +34,135 @@ flag th
3334 description: Build with Template Haskell support
3435 default: True
3536
36−flag benchmarks
37− description: Build benchmarks
38− default: False
39−
40−flag prof
41− description: Compiling with profiling enabled
42− default: False
37+flag old-locale
38+ description: If false then depend on time >= 1.5.
39+ .
40+ If true then depend on time < 1.5 together with old-locale.
41+ default: False
4342
4443Library
45− Build-Depends: base >= 4.4 && < 5,
46− binary >= 0.5 && < 0.7,
47− network-transport >= 0.3 && < 0.4,
48− stm >= 2.4 && < 2.5,
49− transformers >= 0.2 && < 0.4,
50− mtl >= 2.0 && < 2.2,
44+ Build-Depends: base >= 4.6 && < 5,
45+ binary >= 0.6.3 && < 0.10,
46+ hashable >= 1.2.0.5 && < 1.3,
47+ network-transport >= 0.4.1.0 && < 0.6,
48+ stm >= 2.4 && < 2.6,
49+ transformers >= 0.2 && < 0.6,
50+ mtl >= 2.0 && < 2.4,
5151 data-accessor >= 0.2 && < 0.3,
5252 bytestring >= 0.9 && < 0.11,
53− containers >= 0.4 && < 0.6,
54− old-locale >= 1.0 && < 1.1,
55− time >= 1.2 && < 1.5,
56− random >= 1.0 && < 1.1,
57− ghc-prim >= 0.2 && < 0.4,
58− distributed-static >= 0.2 && < 0.3,
59− rank1dynamic >= 0.1 && < 0.2,
60− syb >= 0.3 && < 0.4
53+ random >= 1.0 && < 1.2,
54+ distributed-static >= 0.2 && < 0.4,
55+ rank1dynamic >= 0.1 && < 0.5,
56+ syb >= 0.3 && < 0.8,
57+ exceptions >= 0.5,
58+ containers >= 0.5 && < 0.7,
59+ deepseq >= 1.3.0.1 && < 1.6
6160 Exposed-modules: Control.Distributed.Process,
6261 Control.Distributed.Process.Closure,
63− Control.Distributed.Process.Node,
64− Control.Distributed.Process.Serializable
65− Other-modules: Control.Distributed.Process.Internal.Closure.BuiltIn,
62+ Control.Distributed.Process.Debug,
63+ Control.Distributed.Process.Internal.BiMultiMap,
64+ Control.Distributed.Process.Internal.Closure.BuiltIn,
65+ Control.Distributed.Process.Internal.Closure.Explicit,
6666 Control.Distributed.Process.Internal.CQueue,
6767 Control.Distributed.Process.Internal.Messaging,
6868 Control.Distributed.Process.Internal.Primitives,
69+ Control.Distributed.Process.Internal.Spawn,
6970 Control.Distributed.Process.Internal.StrictContainerAccessors,
7071 Control.Distributed.Process.Internal.StrictList,
7172 Control.Distributed.Process.Internal.StrictMVar,
72− Control.Distributed.Process.Internal.Trace,
7373 Control.Distributed.Process.Internal.Types,
74− Control.Distributed.Process.Internal.WeakTQueue
75− Extensions: RankNTypes,
76− ScopedTypeVariables,
77− FlexibleInstances,
78− UndecidableInstances,
79− ExistentialQuantification,
80− GADTs,
81− GeneralizedNewtypeDeriving,
82− DeriveDataTypeable,
83− CPP,
84− BangPatterns
74+ Control.Distributed.Process.Internal.WeakTQueue,
75+ Control.Distributed.Process.Management,
76+ Control.Distributed.Process.Node,
77+ Control.Distributed.Process.Serializable,
78+ Control.Distributed.Process.UnsafePrimitives
79+ Control.Distributed.Process.Management.Internal.Agent,
80+ Control.Distributed.Process.Management.Internal.Bus,
81+ Control.Distributed.Process.Management.Internal.Types,
82+ Control.Distributed.Process.Management.Internal.Trace.Primitives,
83+ Control.Distributed.Process.Management.Internal.Trace.Remote,
84+ Control.Distributed.Process.Management.Internal.Trace.Types,
85+ Control.Distributed.Process.Management.Internal.Trace.Tracer
8586 ghc-options: -Wall
8687 HS-Source-Dirs: src
87− if flag(th)
88− Build-Depends: template-haskell >= 2.6 && < 2.9
89− Exposed-modules: Control.Distributed.Process.Internal.Closure.TH
90− CPP-Options: -DTemplateHaskellSupport
91−
92−Test-Suite TestCH
93− Type: exitcode-stdio-1.0
94− Main-Is: TestCH.hs
95− Build-Depends: base >= 4.4 && < 5,
96− random >= 1.0 && < 1.1,
97− ansi-terminal >= 0.5 && < 0.6,
98− distributed-process,
99− network-transport >= 0.3 && < 0.4,
100− network-transport-tcp >= 0.3 && < 0.4,
101− binary >= 0.5 && < 0.7,
102− network >= 2.3 && < 2.5,
103− HUnit >= 1.2 && < 1.3,
104− test-framework >= 0.6 && < 0.9,
105− test-framework-hunit >= 0.2.0 && < 0.4
106− Extensions: CPP,
107− ScopedTypeVariables,
108− DeriveDataTypeable,
88+ other-extensions: BangPatterns
89+ CPP
90+ DeriveDataTypeable
91+ DeriveFunctor
92+ DeriveGeneric
93+ ExistentialQuantification
94+ FlexibleInstances
95+ GADTs
10996 GeneralizedNewtypeDeriving
110− ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
111− HS-Source-Dirs: tests
112−
113−Test-Suite TestClosure
114− Type: exitcode-stdio-1.0
115− Main-Is: TestClosure.hs
116− Build-Depends: base >= 4.4 && < 5,
117− random >= 1.0 && < 1.1,
118− ansi-terminal >= 0.5 && < 0.6,
119− distributed-static >= 0.2 && < 0.3,
120− distributed-process,
121− network-transport >= 0.3 && < 0.4,
122− network-transport-tcp >= 0.3 && < 0.4,
123− bytestring >= 0.9 && < 0.11,
124− network >= 2.3 && < 2.5,
125− HUnit >= 1.2 && < 1.3,
126− test-framework >= 0.6 && < 0.9,
127− test-framework-hunit >= 0.2.0 && < 0.4
128− Extensions: CPP,
97+ KindSignatures
98+ MagicHash
99+ PatternGuards
100+ RankNTypes
101+ RecordWildCards
129102 ScopedTypeVariables
130− ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
131− HS-Source-Dirs: tests
132−
133−Test-Suite TestStats
134− Type: exitcode-stdio-1.0
135− Main-Is: TestStats.hs
136− Build-Depends: base >= 4.4 && < 5,
137− random >= 1.0 && < 1.1,
138− ansi-terminal >= 0.5 && < 0.6,
139− containers >= 0.4 && < 0.6,
140− stm >= 2.4 && < 2.5,
141− distributed-process,
142− network-transport >= 0.3 && < 0.4,
143− network-transport-tcp >= 0.3 && < 0.4,
144− binary >= 0.5 && < 0.7,
145− network >= 2.3 && < 2.5,
146− HUnit >= 1.2 && < 1.3,
147− test-framework >= 0.6 && < 0.9,
148− test-framework-hunit >= 0.2.0 && < 0.4
149− Extensions: CPP,
150− ScopedTypeVariables,
151− DeriveDataTypeable,
152− GeneralizedNewtypeDeriving
153− ghc-options: -Wall -debug -eventlog -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
154− HS-Source-Dirs: tests
155−
156−
157−Executable distributed-process-throughput
158− if flag(benchmarks)
159− Build-Depends: base >= 4.4 && < 5,
160− distributed-process,
161− network-transport-tcp >= 0.3 && < 0.4,
162− bytestring >= 0.9 && < 0.11,
163− binary >= 0.5 && < 0.7
164− else
165− buildable: False
166− Main-Is: benchmarks/Throughput.hs
167− ghc-options: -Wall
168− Extensions: BangPatterns
169−
170−Executable distributed-process-latency
171− if flag(benchmarks)
172− Build-Depends: base >= 4.4 && < 5,
173− distributed-process,
174− network-transport-tcp >= 0.3 && < 0.4,
175− bytestring >= 0.9 && < 0.11,
176− binary >= 0.5 && < 0.7
103+ StandaloneDeriving
104+ TypeFamilies
105+ TypeSynonymInstances
106+ UnboxedTuples
107+ UndecidableInstances
108+ if flag(old-locale)
109+ Build-Depends: time < 1.5, old-locale >= 1.0 && <1.1
177110 else
178− buildable: False
179− Main-Is: benchmarks/Latency.hs
180− ghc-options: -Wall
181− Extensions: BangPatterns
182−
183−Executable distributed-process-channels
184− if flag(benchmarks)
185− Build-Depends: base >= 4.4 && < 5,
186− distributed-process,
187− network-transport-tcp >= 0.3 && < 0.4,
188− bytestring >= 0.9 && < 0.11,
189− binary >= 0.5 && < 0.7
190− else
191− buildable: False
192− Main-Is: benchmarks/Channels.hs
193− ghc-options: -Wall
194− Extensions: BangPatterns
195−
196−Executable distributed-process-spawns
197− if flag(benchmarks)
198− Build-Depends: base >= 4.4 && < 5,
199− distributed-process,
200− network-transport-tcp >= 0.3 && < 0.4,
201− bytestring >= 0.9 && < 0.11,
202− binary >= 0.5 && < 0.7
203− else
204− buildable: False
205− Main-Is: benchmarks/Spawns.hs
206− ghc-options: -Wall
207− Extensions: BangPatterns
208−
209−Executable distributed-process-ring
210− if flag(benchmarks)
211− Build-Depends: base >= 4.4 && < 5,
212− distributed-process,
213− network-transport-tcp >= 0.3 && < 0.4,
214− bytestring >= 0.9 && < 0.11,
215− binary >= 0.5 && < 0.7
216− else
217− buildable: False
218− Main-Is: benchmarks/ProcessRing.hs
219− if flag(prof)
220− ghc-options: -Wall -threaded -prof -auto-all -fno-prof-count-entries -rtsopts
221− else
222− ghc-options: -Wall -threaded -O2 -H256m -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
223− Extensions: BangPatterns,
224− ScopedTypeVariables
225−
111+ Build-Depends: time >= 1.5
112+ if flag(th)
113+ other-extensions: TemplateHaskell
114+ Build-Depends: template-haskell >= 2.6
115+ Exposed-modules: Control.Distributed.Process.Internal.Closure.TH
116+ CPP-Options: -DTemplateHaskellSupport
226117
118+-- Tests are in distributed-process-test package, for convenience.
119+
120+benchmark distributed-process-throughput
121+ Type: exitcode-stdio-1.0
122+ Build-Depends: base >= 4.6 && < 5,
123+ distributed-process,
124+ network-transport-tcp >= 0.3 && < 0.7,
125+ bytestring >= 0.9 && < 0.11,
126+ binary >= 0.6.3 && < 0.10
127+ Main-Is: benchmarks/Throughput.hs
128+ ghc-options: -Wall
129+
130+benchmark distributed-process-latency
131+ Type: exitcode-stdio-1.0
132+ Build-Depends: base >= 4.6 && < 5,
133+ distributed-process,
134+ network-transport-tcp >= 0.3 && < 0.7,
135+ bytestring >= 0.9 && < 0.11,
136+ binary >= 0.6.3 && < 0.10
137+ Main-Is: benchmarks/Latency.hs
138+ ghc-options: -Wall
139+
140+benchmark distributed-process-channels
141+ Type: exitcode-stdio-1.0
142+ Build-Depends: base >= 4.6 && < 5,
143+ distributed-process,
144+ network-transport-tcp >= 0.3 && < 0.7,
145+ bytestring >= 0.9 && < 0.11,
146+ binary >= 0.6.3 && < 0.10
147+ Main-Is: benchmarks/Channels.hs
148+ ghc-options: -Wall
149+
150+benchmark distributed-process-spawns
151+ Type: exitcode-stdio-1.0
152+ Build-Depends: base >= 4.6 && < 5,
153+ distributed-process,
154+ network-transport-tcp >= 0.3 && < 0.7,
155+ bytestring >= 0.9 && < 0.11,
156+ binary >= 0.6.3 && < 0.10
157+ Main-Is: benchmarks/Spawns.hs
158+ ghc-options: -Wall
159+
160+benchmark distributed-process-ring
161+ Type: exitcode-stdio-1.0
162+ Build-Depends: base >= 4.6 && < 5,
163+ distributed-process,
164+ network-transport-tcp >= 0.3 && < 0.7,
165+ bytestring >= 0.9 && < 0.11,
166+ binary >= 0.6.3 && < 0.10
167+ Main-Is: benchmarks/ProcessRing.hs
168+ ghc-options: -Wall -threaded -O2 -rtsopts
doc/semantics/CloudHaskellSemantics.texnew file+706
@@ -0,0 +1,706 @@
1+\documentclass{article}
2+\usepackage[margin=2.5cm]{geometry}
3+\usepackage{amsmath, amssymb, stmaryrd}
4+\usepackage{mathpazo, times}
5+\usepackage{float}
6+\usepackage{listings}
7+
8+\newcommand{\sLam}[2]{\lambda {#1} \cdot {#2}}
9+\newcommand{\sApp}[2]{{#1} {#2}}
10+\newcommand{\sBind}[2]{{#1} \mathrel{\texttt{>}\!\!\texttt{>}\!\texttt{=}} {#2}}
11+\newcommand{\sReturn}{\mathtt{return}}
12+\newcommand{\sThrow}[1]{\mathtt{throw} \; {#1}}
13+\newcommand{\sCatch}[2]{\mathtt{catch} \; {#1} \; {#2}}
14+
15+\newcommand{\sExpect}{\mathtt{expect}}
16+\newcommand{\sSend}{\mathtt{send}}
17+\newcommand{\sSpawn}{\mathtt{spawn}}
18+\newcommand{\sLink}{\mathtt{link}}
19+\newcommand{\sReconnect}{\mathtt{reconnect}}
20+\newcommand{\sNewChan}{\mathtt{newChan}}
21+\newcommand{\sSendChan}{\mathtt{sendChan}}
22+\newcommand{\sReceiveChan}{\mathtt{receiveChan}}
23+\newcommand{\sMonitor}{\mathtt{monitor}}
24+
25+\DeclareMathOperator{\sNodeOf}{node}
26+\DeclareMathOperator{\sProcessOf}{process}
27+
28+\newcommand{\sSpawned}{\mathtt{spawned}}
29+
30+\newcommand{\sExtend}[1]{\mathrel{\triangleright_{#1}}}
31+
32+\newcommand{\sPar}{\mathrel{\parallel}}
33+\newcommand{\sProc}[2]{{#1}_{#2}}
34+
35+\newcommand{\sNid}{\ensuremath{\mathit{nid}}}
36+\newcommand{\sPid}{\ensuremath{\mathit{pid}}}
37+\newcommand{\sCid}{\ensuremath{\mathit{cid}}}
38+
39+\newcommand{\sId}{\ensuremath{\mathit{id}}}
40+\newcommand{\sRef}{\ensuremath{\mathit{ref}}}
41+
42+\newcommand{\sLinks}{\ensuremath{\mathit{links}}}
43+\newcommand{\sNode}[3]{\left[{#1} ; {#2}\right]_{#3}}
44+\newcommand{\sSystem}[4]{\left\langle #1 ; #2 ; #3 ; #4 \right\rangle}
45+\newcommand{\sNodes}{\mathcal{N}}
46+\newcommand{\sQueue}{\mathcal{Q}}
47+\newcommand{\sProcesses}{\mathcal{P}}
48+\newcommand{\sBlacklist}{\mathcal{B}}
49+\newcommand{\sMonitors}{\mathcal{M}}
50+
51+\newcommand{\sJust}[1]{\mathtt{Just} \; {#1}}
52+\newcommand{\sNothing}{\mathtt{Nothing}}
53+
54+\newcommand{\sCtxt}[1]{\mathbb{#1}}
55+
56+\newcommand{\sSenders}{\mathit{senders}}
57+
58+\newcommand{\OR}{\mathrel{|}}
59+\newcommand{\where}{\mathrel{|}}
60+
61+\floatstyle{boxed}
62+\restylefloat{figure}
63+
64+\lstset{basicstyle=\ttfamily\small}
65+
66+\begin{document}
67+
68+\title{Cloud Haskell Semantics (DRAFT)}
69+\author{Well-Typed LLP}
70+\date{\today}
71+
72+\maketitle
73+
74+\section{Introduction}
75+
76+Cloud Haskell brings Erlang-style concurrency to Haskell in the form of a
77+library.\footnote{\texttt{http://hackage.haskell.org/package/distributed-process}}
78+The original publication \cite{cloudhaskell} is a good introduction to Cloud
79+Haskell, and is accompanied by an ever-growing number of resouces, collected on
80+the Haskell
81+Wiki.\footnote{\texttt{http://www.haskell.org/haskellwiki/Cloud\_Haskell}} The
82+current document augments the original publication about Cloud Haskell by
83+giving a precise semantics to the Cloud Haskell primitives. It is meant as a
84+reference, not an introduction.
85+
86+The original Cloud Haskell paper stipulates that messages are ``asynchronous,
87+reliable, and buffered'', but does not describe how this can be achieved.
88+Understanding ``reliable'' to mean ``reliable ordered'' (or ``TCP-like''),
89+the reliability of message delivery comes from the reliability of the
90+underlying network protocol---up to a point.
91+
92+The problem is that the underlying network protocol is connection-oriented, but
93+Cloud Haskell is not. Intuitively, when $P$ sends a message to $Q$, we open a
94+reliable-ordered connection from $P$ to $Q$. Reliability of message delivery
95+now follows from reliability of the network protocol, until $P$ somehow gets
96+disconnected from $Q$. If $P$ now sends another message to $Q$, the
97+implementation cannot simply reconnect: after all, some messages that were sent
98+on the first connection might not have been delivered. This means that $P$
99+might send $m_1, m_2, m_3$ to $Q$, but $Q$ will receive $m_1,
100+m_3$.\footnote{Indeed, message passing in Erlang is ordered but unreliable for
101+the same reason \cite{erlang}.}
102+
103+One (non-)solution is for $P$ to buffer all messages it sends to $Q$, and
104+remove messages from this buffer only when $Q$ acknowledges that it received
105+them. On a reconnect $P$ must ask which message $Q$ last received, and
106+retransmit the rest. This means that when $P$ gets disconnected from $Q$, it
107+must infinitely buffer all messages sent to $Q$ (until a connection is
108+reestablished). However, infinite buffering is too strong a requirement;
109+moreover, this is unsatisfactory because it means implementing a reliable
110+protocol on top of the underlying reliable network protocol. We would like a
111+different solution.
112+
113+Instead, Cloud Haskell does \emph{not} attempt to reconnect automatically, but
114+provides a $\sReconnect$ primitive which gives programmers the option of
115+reconnecting manually. This is an explicit acknowledgement from the programmer
116+that message loss might occur, and forces them to consider how such loss might
117+be dealt with.
118+
119+The semantics we present is based on \cite{unified}, which we will refer to the
120+as the ``Unified'' semantics. However, we will present the semantics in a more
121+``Haskell'' style following the semantics for STM \cite{stm}. It differs from
122+the Unified semantics in that
123+%
124+\begin{enumerate}
125+\item We introduce an explicit notion of \textit{reconnecting} (with potential
126+message loss)
127+\item We simplify the semantics: we ``flatten'' sets of nodes of processes as
128+sets of processes (but assume a mapping from process identifiers to node
129+identifiers), do not have per-process mailboxes (but only the system queue or
130+``ether'') and do not have an explicit concept of node controllers
131+\end{enumerate}
132+%
133+Our semantics differs from the STM semantics in that we pretend that the Cloud
134+Haskell \texttt{Process} monad is the top-level monad, and do not consider the
135+\texttt{IO} monad at all. Current imprecisions with respect to the ``real''
136+Cloud Haskell are
137+%
138+\begin{enumerate}
139+\item We ignore the issue of serializability, other than to say that the
140+semantics will get stuck when trying to send a non-serializable payload;
141+consequently, we do not formalize $\mathtt{static}$
142+\item We do not formalize all Cloud Haskell primitives (merging of ports,
143+``advanced messaging'', and others)
144+\item Some of the concepts that we do formalize are lower-level concepts; for
145+instance, the primitive $\sSpawn$ that we formalize is asynchronous (following
146+the Unified semantics); a synchronous construct can be derived.
147+\end{enumerate}
148+
149+\section{Preliminaries}
150+
151+Cloud Haskell Processes run on \emph{nodes}. Processes communicate by sending
152+messages to each other (directly or using typed channels). Processes can also
153+send messages to nodes (for instance, a request to spawn or monitor a process).
154+
155+We assume disjoint countable sets $\mathtt{NodeId}$, $\mathtt{ProcessId}$, and
156+$\mathtt{ChannelId}$, changed over by \sNid, \sPid{} and \sCid{} respectively,
157+and representing process identifiers, node identifiers, and (typed) channel
158+identifiers. We assume the existence of total functions
159+%
160+\begin{equation*}
161+\begin{array}{r@{\;:\;}l}
162+\sNodeOf & (\mathtt{ProcessId} \uplus \mathtt{ChannelId}) \rightarrow \mathtt{NodeId} \\
163+\sProcessOf & \mathtt{ChannelId} \rightarrow \mathtt{ProcessId}
164+\end{array}
165+\end{equation*}
166+%
167+and define
168+$$\mathtt{Identifier} = \mathtt{NodeId} \uplus \mathtt{ProcessId} \uplus \mathtt{ChannelId}$$
169+and let $\sId$ range over $\mathtt{Identifier}$.
170+
171+We represent a process as a pair $\sProc{M}{\sPid}$ of a term $M$ and a process
172+ID $\sPid$. We will denote a set of processes as
173+%
174+ $$\sProc{M}{\sPid} \sPar \cdots \sPar \sProc{N}{\sPid'}$$
175+%
176+A \emph{system}
177+ $\sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}$
178+is a tuple containing a set $\sProcesses$ of processes, a \emph{system
179+queue} $\sQueue$, a \emph{blacklist} $\sBlacklist$, and a set of monitors
180+$\sMonitors$.
181+The set of monitors $\sMonitors$ is a set of tuples
182+ $(\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid, \sRef)$
183+which records that node \sNid{} knows that process $\sPid_\mathit{fr}$ is
184+monitoring $\sId_\mathit{to}$ ($\sRef$ is the monitor reference). The system
185+queue is a set of triples $(\sId_\mathit{to}, \sId_\mathit{fr},
186+\mathit{message})$ of messages that have been sent but not yet processed. The
187+blacklist records disconnections and is represented as a of pairs
188+$(\sId_\mathit{to}, \sId_\mathit{fr})$.
189+
190+\section{Semantics}
191+
192+We follow the STM semantics as closely as possible. The language is the same
193+except for the primitives considered, and we use the same concept of evaluation
194+contexts:
195+
196+\begin{equation*}
197+\begin{array}{llll}
198+\text{value} & V & ::= & \sId \OR
199+ \sLam{x}{M} \OR
200+ \sReturn \; M \OR
201+ \sBind{M}{N} \OR
202+ \sThrow{M} \OR \sCatch{M}{N} \OR \\
203+ &&& \sExpect \OR
204+ \sSend \; \sPid \; M \OR
205+ \sSpawn \; \sNid \; M \OR
206+ \sMonitor \; \sId
207+\\
208+\text{term} & M, N & ::= & x \OR
209+ V \OR
210+ \sApp{M}{N} \OR
211+ \cdots \\
212+\\
213+\text{Evaluation} & \sCtxt{E} & ::= & [] \OR \sBind{\sCtxt{E}}{M} \OR \sCatch{\sCtxt{E}}{M} \\
214+\text{context} & \sCtxt{P}_\sPid & ::= & \sCtxt{E}[]_\sPid \sPar \sProcesses
215+\end{array}
216+\end{equation*}
217+%
218+Indeed, the ``administrative'' transitions are identical
219+(Figure~\ref{fig:administrative}).
220+
221+\begin{figure}
222+\small
223+\begin{equation*}
224+\frac{
225+ \llbracket M \rrbracket = V \qquad
226+ M \neq V
227+}{
228+ M \rightarrow V
229+} \textsc{Eval}
230+\qquad
231+\frac{
232+ M \rightarrow N
233+}{
234+ \sSystem{\sCtxt{P}[M]_\sPid}
235+ {\sQueue}
236+ {\sBlacklist}
237+ {\sMonitors}
238+\rightarrow
239+ \sSystem{\sCtxt{P}[N]_\sPid}
240+ {\sQueue}
241+ {\sBlacklist}
242+ {\sMonitors}
243+} \textsc{Admin}
244+\end{equation*}
245+%
246+\begin{equation*}
247+\begin{array}{r@{\;\rightarrow\;}ll@{\hspace{2em}}r@{\;\rightarrow\;}ll}
248+\sBind{\sReturn \; N}{M} & M \; N & \textsc{Bind} & \sCatch{(\sReturn \; M)}{N} & \sReturn \; M & \textsc{Catch}_1 \\
249+\sBind{\sThrow{N}}{M} & \sThrow{N} & \textsc{Throw} & \sCatch{(\sThrow M)}{N} & N \; M & \textsc{Catch}_2 \\
250+\end{array}
251+\end{equation*}
252+%
253+\caption{\label{fig:administrative}Administrative Transitions}
254+\end{figure}
255+
256+\subsection{Disconnect and Reconnect}
257+
258+Figure~\ref{fig:disconnect} gives the semantics for disconnecting and
259+reconnecting. Rule \textsc{Disconnect} models random network disconnect
260+between nodes $\sNid_1$ and $\sNid_2$. We assume that entire \emph{nodes} get
261+disconnected from each other, not individual processes. \emph{Reconnecting}
262+however is on a per connection basis (\textsc{Recon-Ex}). Connections to and
263+from node controllers can be implicitly reconnected ($\textsc{Recon-Im}_1$ and
264+$\textsc{Recon-Im}_2$).
265+
266+\begin{figure}
267+\small
268+\begin{equation*}
269+\frac{
270+ \sNid_1 \neq \sNid_2
271+}{
272+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}
273+\rightarrow
274+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist \cup (\overline{\sNid_1} \times \overline{\sNid_2})}{\sMonitors}
275+} \textsc{Disconnect}
276+\end{equation*}
277+%
278+\begin{equation*}
279+\frac{
280+}{
281+ \sSystem{\sCtxt{P}[ \sReconnect \; \sId_\mathit{to} ]_{\sPid_\mathit{fr}}}
282+ {\sQueue}
283+ {\sBlacklist}
284+ {\sMonitors}
285+\rightarrow
286+ \sSystem{\sCtxt{P}[ \sReturn \; () ]_\sPid}
287+ {\sQueue}
288+ {\sBlacklist \backslash (\sId_\mathit{to}, \sPid_\mathit{fr})}
289+ {\sMonitors}
290+} \textsc{Recon-Ex}
291+\end{equation*}
292+%
293+\begin{equation*}
294+\frac{
295+}{
296+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist, (\sNid_\mathit{to}, \sId_\mathit{fr})}{\sMonitors}
297+\rightarrow
298+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}
299+} \textsc{Recon-Im}_1
300+\qquad
301+\frac{
302+}{
303+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist, (\sId_\mathit{to}, \sNid_\mathit{fr})}{\sMonitors}
304+\rightarrow
305+ \sSystem{\sProcesses}{\sQueue}{\sBlacklist}{\sMonitors}
306+} \textsc{Recon-Im}_2
307+\end{equation*}
308+%
309+where
310+%
311+\begin{equation*}
312+\overline{\sNid} \subset \mathtt{Identifier}
313+ = \{ \sNid \} \cup
314+ \{ \sPid \where \sNodeOf(\sPid) = \sNid \} \cup
315+ \{ \sCid \where \sNodeOf(\sCid) = \sNid \}
316+\end{equation*}
317+\caption{\label{fig:disconnect}Disconnect and Reconnect}
318+\end{figure}
319+
320+\subsection{Communication}
321+
322+The semantics for the basic primitives are listed in Figure~\ref{fig:basic}.
323+Once a connection has been blacklisted, no further messages can be sent across
324+that connection (until an explicit or implicit reconnect).
325+
326+Message passing is ordered but only for a given sender and receiver; no
327+ordering guarantees exist between messages sent by different processes. For
328+that reason rules \textsc{Expect}, \textsc{ReceiveChan} and \textsc{Spawn-Exec}
329+choose the \emph{first} message of a \emph{randomly chosen} sender.
330+
331+Since the semantics is not type-driven, we represent typed channels simply as
332+an identifier with an annotation whether it is the send-end ($\sCid^s$) or the
333+receive end ($\sCid^r$) of the channel (rules \textsc{NewChan},
334+\textsc{SendChan} and \textsc{ReceiveChan}).
335+
336+Spawning finally is asynchronous. When process $P$ spawns process $Q$ on node
337+$\sNid$ (rule \textsc{Spawn-Async}) a message is sent to node $\sNid$ (which
338+may, of course, never arrive). When the remote node receives the message and
339+actually spawns $Q$ (rule \textsc{Spawn-Exec}) it sends a message back to $P$
340+with $Q$'s process ID. In Cloud Haskell this primitive is called
341+\texttt{spawnAsync}, and the Cloud Haskell \texttt{spawn} primitive is defined
342+in terms of \texttt{spawnAsync} (we do not consider the synchronous version in
343+this document).
344+
345+\begin{figure}
346+\small
347+\begin{equation*}
348+\frac{
349+}{
350+ \sSystem{\sCtxt{P}[ \sSend \; \sPid_\mathit{to} \; M ]_{\sPid_\mathit{fr}}}
351+ {\sQueue}
352+ {\sBlacklist}
353+ {\sMonitors}
354+\rightarrow
355+ \sSystem{\sCtxt{P}[ \sReturn \; () ]_{\sPid_\mathit{fr}}}
356+ {\sQueue \sExtend{\sBlacklist} (\sPid_\mathit{to}, \sPid_\mathit{fr}, M)}
357+ {\sBlacklist}
358+ {\sMonitors}
359+} \textsc{Send}
360+\end{equation*}
361+%
362+\begin{equation*}
363+\frac{
364+ \sId_{\mathit{fr}} \notin \sSenders(\sQueue)
365+}{
366+ \sSystem{\sCtxt{P}[ \sExpect ]_{\sPid_\mathit{to}}}
367+ {\sQueue, (\sPid_\mathit{to}, \sId_\mathit{fr}, M), \sQueue'}
368+ {\sBlacklist}
369+ {\sMonitors}
370+\rightarrow
371+ \sSystem{\sCtxt{P}[ \sReturn \; M ]_{\sPid_\mathit{to}}}
372+ {\sQueue, \sQueue'}
373+ {\sBlacklist}
374+ {\sMonitors}
375+} \textsc{Expect}
376+\end{equation*}
377+%
378+\begin{equation*}
379+\frac{
380+ \sCid \text{ fresh}
381+\qquad
382+ \sProcessOf(\sCid) = \sPid
383+}{
384+ \sSystem{\sCtxt{P}[\sNewChan]_\sPid}
385+ {\sQueue}
386+ {\sBlacklist}
387+ {\sMonitors}
388+\rightarrow
389+ \sSystem{\sCtxt{P}[\sReturn \; (\sCid^s, \sCid^r)]_\sPid}
390+ {\sQueue}
391+ {\sBlacklist}
392+ {\sMonitors}
393+} \textsc{NewChan}
394+\end{equation*}
395+
396+\begin{equation*}
397+\frac{
398+}{
399+ \sSystem{\sCtxt{P}[ \sSendChan \; \sCid_\mathit{to}^s \; M ]_{\sPid_\mathit{fr}}}
400+ {\sQueue}
401+ {\sBlacklist}
402+ {\sMonitors}
403+\rightarrow
404+ \sSystem{\sCtxt{P}[ \sReturn \; () ]_{\sPid_\mathit{fr}}}
405+ {\sQueue \sExtend{\sBlacklist} (\sCid_\mathit{to}, \sPid_\mathit{fr}, M)}
406+ {\sBlacklist}
407+ {\sMonitors}
408+} \textsc{SendChan}
409+\end{equation*}
410+
411+\begin{equation*}
412+\frac{
413+ \sPid_{\mathit{fr}} \notin \sSenders(\sQueue)
414+}{
415+ \sSystem{\sCtxt{P}[ \sReceiveChan \; \sCid_\mathit{to}^r ]_{\sPid}}
416+ {\sQueue, (\sCid_\mathit{to}, \sPid_\mathit{fr}, M), \sQueue'}
417+ {\sBlacklist}
418+ {\sMonitors}
419+\rightarrow
420+ \sSystem{\sCtxt{P}[ \sReturn \; M ]_{\sPid}}
421+ {\sQueue, \sQueue'}
422+ {\sBlacklist}
423+ {\sMonitors}
424+} \textsc{ReceiveChan}
425+\end{equation*}
426+%
427+\begin{equation*}
428+\frac{
429+ \sRef \text{ fresh}
430+}{
431+ \sSystem{\sCtxt{P}[ \sSpawn \; \sNid \; M ]_\sPid}
432+ {\sQueue}
433+ {\sBlacklist}
434+ {\sMonitors}
435+\rightarrow
436+ \sSystem{\sCtxt{P}[ \sReturn \; \sRef ]_\sPid}
437+ {\sQueue \sExtend{\sBlacklist} (\sNid, \sPid, \sSpawn \; \sRef \; M)}
438+ {\sBlacklist}
439+ {\sMonitors}
440+} \textsc{Spawn-Async}
441+\end{equation*}
442+%
443+\begin{equation*}
444+\frac{
445+ \sPid \notin \sSenders(\sQueue)
446+\qquad
447+ \sPid' \text{ fresh}
448+\qquad
449+ \sNodeOf(\sPid') = \sNid
450+}{
451+ \sSystem{\sProcesses}
452+ {\sQueue, (\sNid, \sPid, \sSpawn \; \sRef \; M), \sQueue'}
453+ {\sBlacklist}
454+ {\sMonitors}
455+\rightarrow
456+ \sSystem{\sProcesses \sPar M_{\sPid'}}
457+ {\sQueue, \sQueue' \sExtend{\sBlacklist} (\sPid, \sNid, \sSpawned \; \sRef \; \sPid')}
458+ {\sBlacklist}
459+ {\sMonitors}
460+} \textsc{Spawn-Exec}
461+\end{equation*}
462+%
463+where
464+%
465+\begin{equation*}
466+\begin{array}{l@{\;=\;}l@{\hspace{3em}}l}
467+ \sQueue \sExtend{\sBlacklist} (\sId_\mathit{to}, \sId_\mathit{fr}, M)
468+&
469+ \sQueue, (\sId_\mathit{to}, \sId_\mathit{fr}, M)
470+&
471+ \text{if } (\sId_\mathit{to}, \sId_\mathit{fr}) \notin \sBlacklist
472+\\
473+ \sQueue \sExtend{\sBlacklist} (\sId_\mathit{to}, \sId_\mathit{fr}, M)
474+&
475+ \sQueue
476+&
477+ \text{otherwise}
478+\end{array}
479+\end{equation*}
480+%
481+$(\sExtend{\sBlacklist})$ is only defined for serializable payloads.
482+\caption{\label{fig:basic}Basic Primitives}
483+\end{figure}
484+
485+\subsection{Monitoring}
486+
487+When process $P$ on node $\sNid_P$ wants to monitor process $Q$ on node
488+$\sNid_Q$ \emph{both} nodes must be notified. The local node it notified
489+immediately, and a message is sent to to the remote node (rules
490+\textsc{Mon-Loc} and \textsc{Mon-Rem}). As with all messages, the
491+message to the remote node might be lost. When $P$ is disconnected from $Q$ it
492+is the responsibility of $P$'s local node $\sNid_P$ to notify $P$
493+(\textsc{Mon-Dis}); when $Q$ terminates (\textsc{Mon-Ret}) or crashes
494+(\textsc{Mon-Throw}) it is the responsibility of the remote node $\sNid_Q$ to
495+notify $P$.
496+
497+\begin{figure}
498+\small
499+\begin{equation*}
500+\frac{
501+ \sNid_\mathit{fr} = \sNodeOf(\sPid_\mathit{fr})
502+\qquad
503+ \sNid_\mathit{to} = \sNodeOf(\sId_\mathit{to})
504+\qquad
505+ \sRef \text{ fresh}
506+}{
507+\sSystem{\sCtxt{P}[ \sMonitor \; \sId_\mathit{to} ]_{\sPid_\mathit{fr}}}
508+ {\sQueue}
509+ {\sBlacklist}
510+ {\sMonitors}
511+\rightarrow
512+ \sSystem{\sCtxt{P}[ \sReturn \; \sRef ]_{\sPid_\mathit{fr}}}
513+ {\sQueue \sExtend{\sBlacklist} (\sNid_\mathit{to}, \sPid_\mathit{fr}, \sMonitor \; \sRef \; \sId_\mathit{to})}
514+ {\sBlacklist}
515+ {\sMonitors, (\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid_\mathit{fr}, \sRef)}
516+} \textsc{Mon-Loc}
517+\end{equation*}
518+%
519+\begin{equation*}
520+\frac{
521+ \sPid_\mathit{fr} \notin \sSenders(\sQueue)
522+}{
523+ \sSystem{\sProcesses}
524+ {\sQueue, (\sNid_\mathit{to}, \sPid_\mathit{fr}, \sMonitor \; \sRef \; \sId_\mathit{to}), \sQueue'}
525+ {\sBlacklist}
526+ {\sMonitors}
527+\rightarrow
528+ \sSystem{\sProcesses}
529+ {\sQueue, \sQueue'}
530+ {\sBlacklist}
531+ {\sMonitors, (\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid_\mathit{to}, \sRef)}
532+} \textsc{Mon-Rem}
533+\end{equation*}
534+%
535+\begin{equation*}
536+\frac{
537+ \sNid_\mathit{fr} = \sNodeOf(\sPid_\mathit{fr})
538+\qquad
539+ (\sId_\mathit{to}, \sPid_\mathit{fr}) \in \sBlacklist
540+}{
541+ \sSystem{\sProcesses}
542+ {\sQueue}
543+ {\sBlacklist}
544+ {\sMonitors, (\sId_\mathit{to}, \sPid_\mathit{fr}, \sNid_\mathit{fr}, \sRef)}
545+\rightarrow
546+ \sSystem{\sProcesses}
547+ {\sQueue, (\sPid_\mathit{fr}, \sId_\mathit{to}, \mathtt{discon} \; \sRef)}
548+ {\sBlacklist}
549+ {\sMonitors}
550+} \textsc{Mon-Dis}
551+\end{equation*}
552+%
553+\begin{equation*}
554+\frac{
555+ \sNodeOf(\sPid) = \sNid
556+}{
557+ \sSystem{{(\sReturn \; ())}_\sPid \sPar \sProcesses}
558+ {\sQueue}
559+ {\sBlacklist}
560+ {\sMonitors, (\sPid, \sPid', \sNid, \sRef)}
561+\rightarrow
562+ \sSystem{{(\sReturn \; ())}_\sPid \sPar \sProcesses}
563+ {\sQueue \sExtend{\sBlacklist} (\sPid', \sNid, \mathtt{died} \; \sRef)}
564+ {\sBlacklist}
565+ {\sMonitors}
566+} \textsc{Mon-Ret}
567+\end{equation*}
568+%
569+\begin{equation*}
570+\frac{
571+ \sNodeOf(\sPid) = \sNid
572+}{
573+ \sSystem{{(\sThrow{M})}_\sPid \sPar \sProcesses}
574+ {\sQueue}
575+ {\sBlacklist}
576+ {\sMonitors, (\sPid, \sPid', \sNid, \sRef)}
577+\rightarrow
578+ \sSystem{{(\sThrow{M})}_\sPid \sPar \sProcesses}
579+ {\sQueue \sExtend{\sBlacklist} (\sPid', \sNid, \mathtt{exc} \; \sRef \; M)}
580+ {\sBlacklist}
581+ {\sMonitors}
582+} \textsc{Mon-Throw}
583+\end{equation*}
584+%
585+\begin{equation*}
586+\frac{
587+ \sNodeOf(\sPid) = \sNid
588+\qquad
589+ \sPid \notin \sProcesses
590+}{
591+ \sSystem{\sProcesses}
592+ {\sQueue}
593+ {\sBlacklist}
594+ {\sMonitors, (\sPid, \sPid', \sNid, \sRef)}
595+\rightarrow
596+ \sSystem{\sProcesses}
597+ {\sQueue \sExtend{\sBlacklist} (\sPid', \sNid, \mathtt{unknown} \; \sRef)}
598+ {\sBlacklist}
599+ {\sMonitors}
600+} \textsc{Mon-Unknown}
601+\end{equation*}
602+%
603+\caption{Monitoring}
604+\end{figure}
605+
606+\subsection{Process Termination}
607+
608+The rules for normal and abnormal process termination are defined in
609+Figure~\ref{fig:termination}. When a process crashes it dies
610+silently, unless monitors are setup.
611+
612+\begin{figure}
613+\small
614+%
615+\begin{equation*}
616+\frac{
617+ \sNodeOf(\sPid) = \sNid
618+\qquad
619+ \nexists \; \sPid', \sRef \cdot (\sPid', \sPid, \sNid, \sRef) \in \sMonitors
620+}{
621+ \sSystem{{(\sReturn \; ())}_\sPid \sPar \sProcesses}
622+ {\sQueue}
623+ {\sBlacklist}
624+ {\sMonitors}
625+\rightarrow
626+ \sSystem{\sProcesses}
627+ {\sQueue}
628+ {\sBlacklist}
629+ {\sMonitors}
630+} \textsc{TermRet}
631+\end{equation*}
632+%
633+\begin{equation*}
634+\frac{
635+ \sNodeOf(\sPid) = \sNid
636+\qquad
637+ \nexists \; \sPid', \sRef \cdot (\sPid', \sPid, \sNid, \sRef) \in \sMonitors
638+}{
639+ \sSystem{{(\sThrow{M})}_\sPid \sPar \sProcesses}
640+ {\sQueue}
641+ {\sBlacklist}
642+ {\sMonitors}
643+\rightarrow
644+ \sSystem{\sProcesses}
645+ {\sQueue}
646+ {\sBlacklist}
647+ {\sMonitors}
648+} \textsc{TermThrow}
649+\end{equation*}
650+%
651+\caption{\label{fig:termination}Process Termination}
652+\end{figure}
653+
654+\section{Open Issues}
655+
656+\subsection{Ordering of Monitor/Link Notifications}
657+
658+The semantics as described above, like the original Unified semantics, does not
659+guarantee that messages send from process $P$ to process $Q$ (\textsc{Send})
660+are ordered with respect to the link or monitor notification sent when process
661+$P$ terminates normally or abnormally (\textsc{Mon-Ret}, \textsc{Mon-Throw}). This means that if process $P$ does
662+
663+\begin{lstlisting}
664+receiveWait [
665+ match $ \(Reply reply) -> ...
666+ , match $ \(ProcessMonitorNotification ..) -> ...
667+ ]
668+\end{lstlisting}
669+
670+and process $Q$ does
671+
672+\begin{lstlisting}
673+send pidA (Reply reply)
674+// terminate or indeed throw an exception
675+\end{lstlisting}
676+
677+then the semantics does not guarantee that the reply from process $Q$ will
678+arrive at process $P$ before the monitor notification; hence, this results in
679+an (artificial) race condition in process $P$.
680+
681+One possible solution is to regard such a link notification as a message from
682+process $Q$ to process $P$, which should be ordered along with the other
683+messages.
684+
685+\subsection{Ordering and Typed Channels}
686+
687+The situation is more tricky still than sketched above because we of typed
688+channels. The semantics does not provide ordering guarantees between messages
689+sent directly to the process and messages sent on typed channels. That means
690+that even if we consider a link or monitor notification as a message sent to
691+the process, to be ordered with other messages sent to that process, it is
692+still unordered with respect to messages sent on typed channels. This means
693+that we have similar race conditions\footnote{Even ignoring the fact that we
694+currently don't even provide a way to wait for a message on a typed channel
695+\emph{or} a direct message.}
696+
697+A possible solution is to insist that \emph{all} messages from process $P$ to
698+process $Q$ are ordered, no matter how they are sent. From a implementation
699+point of view, this would entail the use of a single ordered network connection
700+for all messages from $P$ to $Q$, rather than using an ordered connection per
701+typed channel plus one for direct messages.
702+
703+\bibliographystyle{apalike}
704+\bibliography{references}
705+
706+\end{document}
doc/semantics/Makefilenew file+11
@@ -0,0 +1,11 @@
1+CloudHaskellSemantics.pdf: *.tex *.bib
2+ pdflatex CloudHaskellSemantics
3+ bibtex CloudHaskellSemantics
4+ pdflatex CloudHaskellSemantics
5+ pdflatex CloudHaskellSemantics
6+
7+.PHONY: clean
8+clean:
9+ rm -f *.aux *.bbl *.blg *.log
10+
11+# vi:set noexpandtab:
doc/semantics/references.bibnew file+72
@@ -0,0 +1,72 @@
1+@inproceedings{cloudhaskell,
2+ author = {Epstein, Jeff and Black, Andrew P. and Peyton-Jones, Simon},
3+ title = {Towards {H}askell in the cloud},
4+ booktitle = {Proceedings of the 4th ACM symposium on Haskell},
5+ series = {Haskell '11},
6+ year = {2011},
7+ isbn = {978-1-4503-0860-1},
8+ location = {Tokyo, Japan},
9+ pages = {118--129},
10+ numpages = {12},
11+ url = {http://doi.acm.org/10.1145/2034675.2034690},
12+ doi = {10.1145/2034675.2034690},
13+ acmid = {2034690},
14+ publisher = {ACM},
15+ address = {New York, NY, USA},
16+ keywords = {erlang, haskell, message-passing},
17+}
18+
19+@inproceedings{unified,
20+ author = {Svensson, Hans and Fredlund, Lars-{\AA}ke and Benac Earle, Clara},
21+ title = {A unified semantics for future {E}rlang},
22+ booktitle = {Proceedings of the 9th ACM SIGPLAN workshop on Erlang},
23+ series = {Erlang '10},
24+ year = {2010},
25+ isbn = {978-1-4503-0253-1},
26+ location = {Baltimore, Maryland, USA},
27+ pages = {23--32},
28+ numpages = {10},
29+ url = {http://doi.acm.org/10.1145/1863509.1863514},
30+ doi = {10.1145/1863509.1863514},
31+ acmid = {1863514},
32+ publisher = {ACM},
33+ address = {New York, NY, USA},
34+ keywords = {erlang, semantics},
35+}
36+
37+@article{stm,
38+ author = {Harris, Tim and Marlow, Simon and Jones, Simon Peyton and Herlihy, Maurice},
39+ title = {Composable memory transactions},
40+ journal = {Commun. ACM},
41+ issue_date = {August 2008},
42+ volume = {51},
43+ number = {8},
44+ month = aug,
45+ year = {2008},
46+ issn = {0001-0782},
47+ pages = {91--100},
48+ numpages = {10},
49+ url = {http://doi.acm.org/10.1145/1378704.1378725},
50+ doi = {10.1145/1378704.1378725},
51+ acmid = {1378725},
52+ publisher = {ACM},
53+ address = {New York, NY, USA},
54+}
55+
56+@inproceedings{erlang,
57+ author = {Svensson, Hans and Fredlund, Lars-\AAke},
58+ title = {A more accurate semantics for distributed erlang},
59+ booktitle = {Proceedings of the 2007 SIGPLAN workshop on ERLANG Workshop},
60+ series = {ERLANG '07},
61+ year = {2007},
62+ isbn = {978-1-59593-675-2},
63+ location = {Freiburg, Germany},
64+ pages = {43--54},
65+ numpages = {12},
66+ url = {http://doi.acm.org/10.1145/1292520.1292528},
67+ doi = {10.1145/1292520.1292528},
68+ acmid = {1292528},
69+ publisher = {ACM},
70+ address = {New York, NY, USA},
71+ keywords = {distributed systems, erlang, model checking, semantics, verification},
72+}
src/Control/Distributed/Process.hs+110−140
@@ -1,8 +1,10 @@
1+{-# LANGUAGE CPP #-}
12{- | [Cloud Haskell]
23
34This is an implementation of Cloud Haskell, as described in
45/Towards Haskell in the Cloud/ by Jeff Epstein, Andrew Black, and Simon
5−Peyton Jones (<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),
6+Peyton Jones (see
7+<http://research.microsoft.com/en-us/um/people/simonpj/papers/parallel/>),
68although some of the details are different. The precise message passing
79semantics are based on /A unified semantics for future Erlang/ by Hans
810Svensson, Lars-Åke Fredlund and Clara Benac Earle.
@@ -23,6 +25,7 @@ module Control.Distributed.Process
2325 , liftIO -- Reexported for convenience
2426 -- * Basic messaging
2527 , send
28+ , usend
2629 , expect
2730 , expectTimeout
2831 -- * Channels
@@ -35,6 +38,13 @@ module Control.Distributed.Process
3538 , receiveChanTimeout
3639 , mergePortsBiased
3740 , mergePortsRR
41+ -- * Unsafe messaging variants
42+ , unsafeSend
43+ , unsafeUSend
44+ , unsafeSendChan
45+ , unsafeNSend
46+ , unsafeNSendRemote
47+ , unsafeWrapMessage
3848 -- * Advanced messaging
3949 , Match
4050 , receiveWait
@@ -45,13 +55,22 @@ module Control.Distributed.Process
4555 , matchAny
4656 , matchAnyIf
4757 , matchChan
58+ , matchSTM
4859 , Message
4960 , matchMessage
5061 , matchMessageIf
62+ , isEncoded
5163 , wrapMessage
5264 , unwrapMessage
5365 , handleMessage
66+ , handleMessageIf
67+ , handleMessage_
68+ , handleMessageIf_
5469 , forward
70+ , uforward
71+ , delegate
72+ , relay
73+ , proxy
5574 -- * Process management
5675 , spawn
5776 , call
@@ -68,6 +87,9 @@ module Control.Distributed.Process
6887 , getSelfNode
6988 , ProcessInfo(..)
7089 , getProcessInfo
90+ , NodeStats(..)
91+ , getNodeStats
92+ , getLocalNodeStats
7193 -- * Monitoring and linking
7294 , link
7395 , linkNode
@@ -80,9 +102,8 @@ module Control.Distributed.Process
80102 , monitorPort
81103 , unmonitor
82104 , withMonitor
105+ , withMonitor_
83106 , MonitorRef -- opaque
84− , ProcessExitException()
85− , exitSource
86107 , ProcessLinkException(..)
87108 , NodeLinkException(..)
88109 , PortLinkException(..)
@@ -118,6 +139,7 @@ module Control.Distributed.Process
118139 , catches
119140 , try
120141 , mask
142+ , mask_
121143 , onException
122144 , bracket
123145 , bracket_
@@ -129,31 +151,30 @@ module Control.Distributed.Process
129151 , spawnMonitor
130152 , spawnChannel
131153 , DidSpawn(..)
132− , nullProcessId
133154 -- * Local versions of 'spawn'
134155 , spawnLocal
135156 , spawnChannelLocal
157+ , callLocal
136158 -- * Reconnecting
137159 , reconnect
138160 , reconnectPort
139161 ) where
140162
141−#if ! MIN_VERSION_base(4,6,0)
142−import Prelude hiding (catch)
143−#endif
144−
145−import Data.Typeable (Typeable)
146163import Control.Monad.IO.Class (liftIO)
147−import Control.Applicative ((<$>))
164+import Control.Applicative
148165import Control.Monad.Reader (ask)
149−import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
166+import Control.Concurrent (killThread)
167+import Control.Concurrent.MVar
168+ ( MVar
169+ , newEmptyMVar
170+ , takeMVar
171+ , putMVar
172+ )
150173import Control.Distributed.Static
151174 ( Closure
152175 , closure
153176 , Static
154177 , RemoteTable
155− , closureCompose
156− , staticClosure
157178 )
158179import Control.Distributed.Process.Internal.Types
159180 ( NodeId(..)
@@ -163,8 +184,6 @@ import Control.Distributed.Process.Internal.Types
163184 , ProcessMonitorNotification(..)
164185 , NodeMonitorNotification(..)
165186 , PortMonitorNotification(..)
166− , ProcessExitException()
167− , exitSource
168187 , ProcessLinkException(..)
169188 , NodeLinkException(..)
170189 , PortLinkException(..)
@@ -179,24 +198,13 @@ import Control.Distributed.Process.Internal.Types
179198 , RegisterReply(..)
180199 , LocalProcess(processNode)
181200 , Message
182− , nullProcessId
183− )
184−import Control.Distributed.Process.Serializable (Serializable, SerializableDict)
185−import Control.Distributed.Process.Internal.Closure.BuiltIn
186− ( sdictSendPort
187− , sndStatic
188− , idCP
189− , seqCP
190− , bindCP
191− , splitCP
192− , cpLink
193− , cpSend
194− , cpNewChan
195− , cpDelay
201+ , localProcessWithId
196202 )
203+import Control.Distributed.Process.Serializable (Serializable)
197204import Control.Distributed.Process.Internal.Primitives
198205 ( -- Basic messaging
199206 send
207+ , usend
200208 , expect
201209 -- Channels
202210 , newChan
@@ -204,6 +212,11 @@ import Control.Distributed.Process.Internal.Primitives
204212 , receiveChan
205213 , mergePortsBiased
206214 , mergePortsRR
215+ , unsafeSend
216+ , unsafeUSend
217+ , unsafeSendChan
218+ , unsafeNSend
219+ , unsafeNSendRemote
207220 -- Advanced messaging
208221 , Match
209222 , receiveWait
@@ -214,12 +227,22 @@ import Control.Distributed.Process.Internal.Primitives
214227 , matchAny
215228 , matchAnyIf
216229 , matchChan
230+ , matchSTM
217231 , matchMessage
218232 , matchMessageIf
233+ , isEncoded
219234 , wrapMessage
235+ , unsafeWrapMessage
220236 , unwrapMessage
221237 , handleMessage
238+ , handleMessageIf
239+ , handleMessage_
240+ , handleMessageIf_
222241 , forward
242+ , uforward
243+ , delegate
244+ , relay
245+ , proxy
223246 -- Process management
224247 , terminate
225248 , ProcessTerminationException(..)
@@ -232,6 +255,9 @@ import Control.Distributed.Process.Internal.Primitives
232255 , getSelfNode
233256 , ProcessInfo(..)
234257 , getProcessInfo
258+ , NodeStats(..)
259+ , getNodeStats
260+ , getLocalNodeStats
235261 -- Monitoring and linking
236262 , link
237263 , linkNode
@@ -244,6 +270,7 @@ import Control.Distributed.Process.Internal.Primitives
244270 , monitorPort
245271 , unmonitor
246272 , withMonitor
273+ , withMonitor_
247274 -- Logging
248275 , say
249276 -- Registry
@@ -266,6 +293,7 @@ import Control.Distributed.Process.Internal.Primitives
266293 , catches
267294 , try
268295 , mask
296+ , mask_
269297 , onException
270298 , bracket
271299 , bracket_
@@ -279,6 +307,30 @@ import Control.Distributed.Process.Internal.Primitives
279307 , reconnectPort
280308 )
281309import Control.Distributed.Process.Node (forkProcess)
310+import Control.Distributed.Process.Internal.Types
311+ ( processThread
312+ , withValidLocalState
313+ )
314+import Control.Distributed.Process.Internal.Spawn
315+ ( -- Spawning Processes/Channels
316+ spawn
317+ , spawnLink
318+ , spawnMonitor
319+ , spawnChannel
320+ , spawnSupervised
321+ , call
322+ )
323+import qualified Control.Monad.Catch as Catch
324+
325+#if MIN_VERSION_base(4,6,0)
326+import Prelude
327+#else
328+import Prelude hiding (catch)
329+#endif
330+import qualified Control.Exception as Exception (onException)
331+import Data.Accessor ((^.))
332+import Data.Foldable (forM_)
333+
282334
283335-- INTERNAL NOTES
284336--
@@ -290,7 +342,11 @@ import Control.Distributed.Process.Node (forkProcess)
290342-- 2. 'send' may block (when the system TCP buffers are full, while we are
291343-- trying to establish a connection to the remote endpoint, etc.) but its
292344-- return does not imply that the remote process received the message (much
293−-- less processed it)
345+-- less processed it). When 'send' targets a local process, it is dispatched
346+-- via the node controller however, in which cases it will /not/ block. This
347+-- isn't part of the semantics, so it should be ok. The ordering is maintained
348+-- because the ctrlChannel still has FIFO semantics with regards interactions
349+-- between two disparate forkIO threads.
294350--
295351-- 3. Message delivery is reliable and ordered. That means that if process A
296352-- sends messages m1, m2, m3 to process B, B will either arrive all three
@@ -339,116 +395,6 @@ import Control.Distributed.Process.Node (forkProcess)
339395-- [6] "Delivery of Messages", post on erlang-questions
340396-- http://erlang.org/pipermail/erlang-questions/2012-February/064767.html
341397
342−--------------------------------------------------------------------------------
343−-- Primitives are defined in a separate module; here we only define derived --
344−-- constructs --
345−--------------------------------------------------------------------------------
346−
347−-- | Spawn a process
348−--
349−-- For more information about 'Closure', see
350−-- "Control.Distributed.Process.Closure".
351−--
352−-- See also 'call'.
353−spawn :: NodeId -> Closure (Process ()) -> Process ProcessId
354−spawn nid proc = do
355− us <- getSelfPid
356− mRef <- monitorNode nid
357− sRef <- spawnAsync nid (cpDelay us proc)
358− receiveWait [
359− matchIf (\(DidSpawn ref _) -> ref == sRef) $ \(DidSpawn _ pid) -> do
360− unmonitor mRef
361− send pid ()
362− return pid
363− , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef) $ \_ ->
364− return (nullProcessId nid)
365− ]
366−
367−-- | Spawn a process and link to it
368−--
369−-- Note that this is just the sequential composition of 'spawn' and 'link'.
370−-- (The "Unified" semantics that underlies Cloud Haskell does not even support
371−-- a synchronous link operation)
372−spawnLink :: NodeId -> Closure (Process ()) -> Process ProcessId
373−spawnLink nid proc = do
374− pid <- spawn nid proc
375− link pid
376− return pid
377−
378−-- | Like 'spawnLink', but monitor the spawned process
379−spawnMonitor :: NodeId -> Closure (Process ()) -> Process (ProcessId, MonitorRef)
380−spawnMonitor nid proc = do
381− pid <- spawn nid proc
382− ref <- monitor pid
383− return (pid, ref)
384−
385−-- | Run a process remotely and wait for it to reply
386−--
387−-- We monitor the remote process: if it dies before it can send a reply, we die
388−-- too.
389−--
390−-- For more information about 'Static', 'SerializableDict', and 'Closure', see
391−-- "Control.Distributed.Process.Closure".
392−--
393−-- See also 'spawn'.
394−call :: Serializable a
395− => Static (SerializableDict a)
396− -> NodeId
397− -> Closure (Process a)
398− -> Process a
399−call dict nid proc = do
400− us <- getSelfPid
401− (pid, mRef) <- spawnMonitor nid (proc `bindCP` cpSend dict us)
402− -- We are guaranteed to receive the reply before the monitor notification
403− -- (if a reply is sent at all)
404− -- NOTE: This might not be true if we switch to unreliable delivery.
405− mResult <- receiveWait
406− [ match (return . Right)
407− , matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)
408− (\(ProcessMonitorNotification _ _ reason) -> return (Left reason))
409− ]
410− case mResult of
411− Right a -> do
412− -- Wait for the monitor message so that we the mailbox doesn't grow
413− receiveWait
414− [ matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)
415− (\(ProcessMonitorNotification {}) -> return ())
416− ]
417− -- Clean up connection to pid
418− reconnect pid
419− return a
420− Left err ->
421− fail $ "call: remote process died: " ++ show err
422−
423−-- | Spawn a child process, have the child link to the parent and the parent
424−-- monitor the child
425−spawnSupervised :: NodeId
426− -> Closure (Process ())
427− -> Process (ProcessId, MonitorRef)
428−spawnSupervised nid proc = do
429− us <- getSelfPid
430− them <- spawn nid (cpLink us `seqCP` proc)
431− ref <- monitor them
432− return (them, ref)
433−
434−-- | Spawn a new process, supplying it with a new 'ReceivePort' and return
435−-- the corresponding 'SendPort'.
436−spawnChannel :: forall a. Typeable a => Static (SerializableDict a)
437− -> NodeId
438− -> Closure (ReceivePort a -> Process ())
439− -> Process (SendPort a)
440−spawnChannel dict nid proc = do
441− us <- getSelfPid
442− _ <- spawn nid (go us)
443− expect
444− where
445− go :: ProcessId -> Closure (Process ())
446− go pid = cpNewChan dict
447− `bindCP`
448− (cpSend (sdictSendPort dict) pid `splitCP` proc)
449− `bindCP`
450− (idCP `closureCompose` staticClosure sndStatic)
451−
452398--------------------------------------------------------------------------------
453399-- Local versions of spawn --
454400--------------------------------------------------------------------------------
@@ -475,3 +421,27 @@ spawnChannelLocal proc = do
475421 liftIO $ putMVar mvar sport
476422 proc rport
477423 takeMVar mvar
424+
425+-- | Local version of 'call'. Running a process in this way isolates it from
426+-- messages sent to the caller process, and also allows silently dropping late
427+-- or duplicate messages sent to the isolated process after it exits.
428+-- Silently dropping messages may not always be the best approach.
429+callLocal :: Process a -> Process a
430+callLocal proc = Catch.mask $ \release -> do
431+ mv <- liftIO newEmptyMVar :: Process (MVar (Either Catch.SomeException a))
432+ child <- spawnLocal $ Catch.try (release proc) >>= liftIO . putMVar mv
433+ lproc <- ask
434+ liftIO $ do
435+ rs <- Exception.onException (takeMVar mv) $ Catch.uninterruptibleMask_ $
436+ -- Exceptions need to be prevented from interrupting the clean up or
437+ -- the original exception which caused entering the handler could be
438+ -- forgotten. For instance, this could have a problematic effect
439+ -- when the original exception was meant to kill the thread and the
440+ -- second exception doesn't (like the exception thrown by
441+ -- 'System.Timeout.timeout').
442+ do mchildThreadId <- withValidLocalState (processNode lproc) $
443+ \vst -> return $ fmap processThread $
444+ vst ^. localProcessWithId (processLocalId child)
445+ forM_ mchildThreadId killThread
446+ takeMVar mv
447+ either Catch.throwM return rs
src/Control/Distributed/Process/Closure.hs+22
@@ -163,6 +163,8 @@ module Control.Distributed.Process.Closure
163163 , sdictUnit
164164 , sdictProcessId
165165 , sdictSendPort
166+ , sdictStatic
167+ , sdictClosure
166168 -- * The CP type and associated combinators
167169 , CP
168170 , idCP
@@ -173,9 +175,17 @@ module Control.Distributed.Process.Closure
173175 -- * CP versions of Cloud Haskell primitives
174176 , cpLink
175177 , cpUnlink
178+ , cpRelay
176179 , cpSend
177180 , cpExpect
178181 , cpNewChan
182+ -- * Working with static values and closures (without Template Haskell)
183+ , RemoteRegister
184+ , MkTDict(..)
185+ , mkStaticVal
186+ , mkClosureValSingle
187+ , mkClosureVal
188+ , call'
179189#ifdef TemplateHaskellSupport
180190 -- * Template Haskell support for creating static values and closures
181191 , remotable
@@ -195,6 +205,8 @@ import Control.Distributed.Process.Internal.Closure.BuiltIn
195205 , sdictUnit
196206 , sdictProcessId
197207 , sdictSendPort
208+ , sdictStatic
209+ , sdictClosure
198210 -- The CP type and associated combinators
199211 , CP
200212 , idCP
@@ -205,10 +217,20 @@ import Control.Distributed.Process.Internal.Closure.BuiltIn
205217 -- CP versions of Cloud Haskell primitives
206218 , cpLink
207219 , cpUnlink
220+ , cpRelay
208221 , cpSend
209222 , cpExpect
210223 , cpNewChan
211224 )
225+import Control.Distributed.Process.Internal.Closure.Explicit
226+ (
227+ RemoteRegister
228+ , MkTDict(..)
229+ , mkStaticVal
230+ , mkClosureValSingle
231+ , mkClosureVal
232+ , call'
233+ )
212234#ifdef TemplateHaskellSupport
213235import Control.Distributed.Process.Internal.Closure.TH
214236 ( remotable
src/Control/Distributed/Process/Debug.hsnew file+288
@@ -0,0 +1,288 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+{-# LANGUAGE RankNTypes #-}
4+-----------------------------------------------------------------------------
5+-- |
6+-- Module : Control.Distributed.Process.Debug
7+-- Copyright : (c) Well-Typed / Tim Watson
8+-- License : BSD3 (see the file LICENSE)
9+--
10+-- Maintainer : Tim Watson <watson.timothy@gmail.com>
11+-- Stability : experimental
12+-- Portability : non-portable (requires concurrency)
13+--
14+-- [Tracing/Debugging Facilities]
15+--
16+-- Cloud Haskell provides a general purpose tracing mechanism, allowing a
17+-- user supplied /tracer process/ to receive messages when certain classes of
18+-- system events occur. It's possible to use this facility to aid in debugging
19+-- and/or perform other diagnostic tasks to a program at runtime.
20+--
21+-- [Enabling Tracing]
22+--
23+-- Throughout the lifecycle of a local node, the distributed-process runtime
24+-- generates /trace events/, describing internal runtime activities such as
25+-- the spawning and death of processes, message sending, delivery and so on.
26+-- See the 'MxEvent' type's documentation for a list of all the published
27+-- event types, which correspond directly to the types of /management/ events.
28+-- Users can additionally publish custom trace events in the form of
29+-- 'MxLog' log messages or pass custom (i.e., completely user defined)
30+-- event data using the 'traceMessage' function.
31+--
32+-- All published traces are forwarded to a /tracer process/, which can be
33+-- specified (and changed) at runtime using 'traceEnable'. Some pre-defined
34+-- tracer processes are provided for conveniently printing to stderr, a log file
35+-- or the GHC eventlog.
36+--
37+-- If a tracer process crashes, no attempt is made to restart it.
38+--
39+-- [Working with multiple tracer processes]
40+--
41+-- The tracing facility only ever writes to a single tracer process. This
42+-- invariant insulates the tracer controller and ensures a fast path for
43+-- handling all trace events. /This/ module provides facilities for layering
44+-- trace handlers using Cloud Haskell's built-in delegation primitives.
45+--
46+-- The 'startTracer' function wraps the registered @tracer@ process with the
47+-- supplied handler and also forwards trace events to the original tracer.
48+-- The corresponding 'stopTracer' function terminates tracer processes in
49+-- reverse of the order in which they were started, and re-registers the
50+-- previous tracer process.
51+--
52+-- [Built in tracers]
53+--
54+-- The built in tracers provide a simple /logging/ facility that writes trace
55+-- events out to either a log file, @stderr@ or the GHC eventlog. These tracers
56+-- can be configured using environment variables, or specified manually using
57+-- the 'traceEnable' function.
58+--
59+-- When a new local node is started, the contents of several environment
60+-- variables are checked to determine which default tracer process is selected.
61+-- If none of these variables is set, a no-op tracer process is installed,
62+-- which effectively ignores all trace messages. Note that in this case,
63+-- trace events are still generated and passed through the system.
64+-- Only one default tracer will be chosen - the first that contains a (valid)
65+-- value. These environment variables, in the order they're examined, are:
66+--
67+-- 1. @DISTRIBUTED_PROCESS_TRACE_FILE@
68+-- This is checked for a valid file path. If it exists and the file can be
69+-- opened for writing, all trace output will be directed thence. If the supplied
70+-- path is invalid, or the file is unavailable for writing, this tracer will not
71+-- be selected.
72+--
73+-- 2. @DISTRIBUTED_PROCESS_TRACE_CONSOLE@
74+-- This is checked for /any/ non-empty value. If set, then all trace output will
75+-- be directed to the system logger process.
76+--
77+-- 3. @DISTRIBUTED_PROCESS_TRACE_EVENTLOG@
78+-- This is checked for /any/ non-empty value. If set, all internal traces are
79+-- written to the GHC eventlog.
80+--
81+-- By default, the built in tracers will ignore all trace events! In order to
82+-- enable tracing the incoming 'MxEvent' stream, the @DISTRIBUTED_PROCESS_TRACE_FLAGS@
83+-- environment variable accepts the following flags, which enable tracing specific
84+-- event types:
85+--
86+-- * @p@ = trace the spawning of new processes
87+-- * @d@ = trace the death of processes
88+-- * @n@ = trace registration of names (i.e., named processes)
89+-- * @u@ = trace un-registration of names (i.e., named processes)
90+-- * @s@ = trace the sending of messages to other processes
91+-- * @r@ = trace the receipt of messages from other processes
92+-- * @l@ = trace node up/down events
93+--
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
96+-- (so as to avoid interleaving), a file trace configured for the master node
97+-- will prevent slaves from tracing to the file. They will need to fall back to
98+-- the console or eventlog tracers instead, which can be accomplished by setting
99+-- one of these environment variables /as well/, since the latter will only be
100+-- selected on slaves (when the file tracer selection fails).
101+--
102+-- Support for writing to the eventlog requires specific intervention to work,
103+-- without which, written traces are silently dropped/ignored and no output will
104+-- be generated. The GHC eventlog documentation provides information about
105+-- enabling, viewing and working with event traces at
106+-- <http://hackage.haskell.org/trac/ghc/wiki/EventLog>.
107+--
108+module Control.Distributed.Process.Debug
109+ ( -- * Exported Data Types
110+ TraceArg(..)
111+ , TraceFlags(..)
112+ , TraceSubject(..)
113+ -- * Configuring Tracing
114+ , enableTrace
115+ , enableTraceAsync
116+ , disableTrace
117+ , withTracer
118+ , withFlags
119+ , getTraceFlags
120+ , setTraceFlags
121+ , setTraceFlagsAsync
122+ , defaultTraceFlags
123+ , traceOn
124+ , traceOnly
125+ , traceOff
126+ -- * Debugging
127+ , startTracer
128+ , stopTracer
129+ -- * Sending Custom Trace Data
130+ , traceLog
131+ , traceLogFmt
132+ , traceMessage
133+ -- * Working with remote nodes
134+ , Remote.remoteTable
135+ , Remote.startTraceRelay
136+ , Remote.setTraceFlagsRemote
137+ -- * Built in tracers
138+ , systemLoggerTracer
139+ , logfileTracer
140+ , eventLogTracer
141+ )
142+ where
143+
144+import Control.Applicative
145+import Control.Distributed.Process.Internal.Primitives
146+ ( proxy
147+ , die
148+ , whereis
149+ , send
150+ , receiveWait
151+ , matchIf
152+ , monitor
153+ )
154+import Control.Distributed.Process.Internal.Types
155+ ( ProcessId
156+ , Process
157+ , LocalProcess(..)
158+ , ProcessMonitorNotification(..)
159+ )
160+import Control.Distributed.Process.Management.Internal.Types
161+ ( MxEvent(..)
162+ )
163+import Control.Distributed.Process.Management.Internal.Trace.Types
164+ ( TraceArg(..)
165+ , TraceFlags(..)
166+ , TraceSubject(..)
167+ , defaultTraceFlags
168+ )
169+import Control.Distributed.Process.Management.Internal.Trace.Tracer
170+ ( systemLoggerTracer
171+ , logfileTracer
172+ , eventLogTracer
173+ )
174+import Control.Distributed.Process.Management.Internal.Trace.Primitives
175+ ( withRegisteredTracer
176+ , enableTrace
177+ , enableTraceAsync
178+ , disableTrace
179+ , setTraceFlags
180+ , setTraceFlagsAsync
181+ , getTraceFlags
182+ , traceOn
183+ , traceOff
184+ , traceOnly
185+ , traceLog
186+ , traceLogFmt
187+ , traceMessage
188+ )
189+import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Remote
190+import Control.Distributed.Process.Node
191+
192+import Control.Exception (SomeException)
193+
194+import Control.Monad.IO.Class (liftIO)
195+import Control.Monad.Reader (ask)
196+import Control.Monad.Catch (finally, try)
197+
198+import Data.Binary()
199+
200+import Prelude
201+
202+--------------------------------------------------------------------------------
203+-- Debugging/Tracing API --
204+--------------------------------------------------------------------------------
205+
206+-- | Starts a new tracer, using the supplied trace function.
207+-- Only one tracer can be registered at a time, however /this/ function overlays
208+-- the registered tracer with the supplied handler, allowing the user to layer
209+-- multiple tracers on top of one another, with trace events forwarded down
210+-- through all the layers in turn. Once the top layer is stopped, the user
211+-- is responsible for re-registering the original (prior) tracer pid before
212+-- terminating. See 'withTracer' for a mechanism that handles that.
213+startTracer :: (MxEvent -> Process ()) -> Process ProcessId
214+startTracer handler = do
215+ withRegisteredTracer $ \pid -> do
216+ node <- processNode <$> ask
217+ newPid <- liftIO $ forkProcess node $ traceProxy pid handler
218+ enableTrace newPid -- invokes sync + registration
219+ return newPid
220+
221+-- | Evaluate @proc@ with tracing enabled via @handler@, and immediately
222+-- disable tracing thereafter, before giving the result (or exception
223+-- in case of failure).
224+withTracer :: forall a.
225+ (MxEvent -> Process ())
226+ -> Process a
227+ -> Process (Either SomeException a)
228+withTracer handler proc = do
229+ previous <- whereis "tracer"
230+ tracer <- startTracer handler
231+ finally (try proc)
232+ (stopTracing tracer previous)
233+ where
234+ stopTracing :: ProcessId -> Maybe ProcessId -> Process ()
235+ stopTracing tracer previousTracer = do
236+ case previousTracer of
237+ Nothing -> return ()
238+ Just _ -> do
239+ ref <- monitor tracer
240+ send tracer MxTraceDisable
241+ receiveWait [
242+ matchIf (\(ProcessMonitorNotification ref' _ _) -> ref == ref')
243+ (\_ -> return ())
244+ ]
245+
246+-- | Evaluate @proc@ with the supplied flags enabled. Any previously set
247+-- trace flags are restored immediately afterwards.
248+withFlags :: forall a.
249+ TraceFlags
250+ -> Process a
251+ -> Process (Either SomeException a)
252+withFlags flags proc = do
253+ oldFlags <- getTraceFlags
254+ finally (setTraceFlags flags >> try proc)
255+ (setTraceFlags oldFlags)
256+
257+traceProxy :: ProcessId -> (MxEvent -> Process ()) -> Process ()
258+traceProxy pid act = do
259+ proxy pid $ \(ev :: MxEvent) ->
260+ case ev of
261+ (MxTraceTakeover _) -> return False
262+ MxTraceDisable -> die "disabled"
263+ _ -> act ev >> return True
264+
265+-- | Stops a user supplied tracer started with 'startTracer'.
266+-- Note that only one tracer process can be active at any given time.
267+-- This process will stop the last process started with 'startTracer'.
268+-- If 'startTracer' is called multiple times, successive calls to this
269+-- function will stop the tracers in the reverse order which they were
270+-- started.
271+--
272+-- This function will never stop the system tracer (i.e., the tracer
273+-- initially started when the node is created), therefore once all user
274+-- supplied tracers (i.e., processes started via 'startTracer') have exited,
275+-- subsequent calls to this function will have no effect.
276+--
277+-- If the last tracer to have been registered was not started
278+-- with 'startTracer' then the behaviour of this function is /undefined/.
279+stopTracer :: Process ()
280+stopTracer =
281+ withRegisteredTracer $ \pid -> do
282+ -- we need to avoid killing the initial (base) tracer, as
283+ -- nothing we rely on having exactly 1 registered tracer
284+ -- process at all times.
285+ basePid <- whereis "tracer.initial"
286+ case basePid == (Just pid) of
287+ True -> return ()
288+ False -> send pid MxTraceDisable
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+72−15
@@ -1,13 +1,18 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveFunctor #-}
3+{-# LANGUAGE BangPatterns #-}
4+{-# LANGUAGE MagicHash, UnboxedTuples, PatternGuards, ScopedTypeVariables, RankNTypes #-}
15-- | Concurrent queue for single reader, single writer
2−{-# LANGUAGE MagicHash, UnboxedTuples, PatternGuards #-}
36module Control.Distributed.Process.Internal.CQueue
47 ( CQueue
58 , BlockSpec(..)
69 , MatchOn(..)
710 , newCQueue
811 , enqueue
12+ , enqueueSTM
913 , dequeue
1014 , mkWeakCQueue
15+ , queueSize
1116 ) where
1217
1318import Prelude hiding (length, reverse)
@@ -15,10 +20,14 @@ import Control.Concurrent.STM
1520 ( atomically
1621 , STM
1722 , TChan
23+ , TVar
24+ , modifyTVar'
1825 , tryReadTChan
1926 , newTChan
27+ , newTVarIO
2028 , writeTChan
2129 , readTChan
30+ , readTVarIO
2231 , orElse
2332 , retry
2433 )
@@ -36,35 +45,56 @@ import Control.Distributed.Process.Internal.StrictList
3645 , append
3746 )
3847import Data.Maybe (fromJust)
48+import Data.Traversable (traverse)
3949import GHC.MVar (MVar(MVar))
40−import GHC.IO (IO(IO))
41−import GHC.Prim (mkWeak#)
50+import GHC.IO (IO(IO), unIO)
51+import GHC.Exts (mkWeak#)
4252import GHC.Weak (Weak(Weak))
4353
4454-- We use a TCHan rather than a Chan so that we have a non-blocking read
4555data CQueue a = CQueue (StrictMVar (StrictList a)) -- Arrived
4656 (TChan a) -- Incoming
57+ (TVar Int) -- Queue size
4758
4859newCQueue :: IO (CQueue a)
49−newCQueue = CQueue <$> newMVar Nil <*> atomically newTChan
60+newCQueue = CQueue <$> newMVar Nil <*> atomically newTChan <*> newTVarIO 0
5061
5162-- | Enqueue an element
5263--
5364-- Enqueue is strict.
5465enqueue :: CQueue a -> a -> IO ()
55−enqueue (CQueue _arrived incoming) !a = atomically $ writeTChan incoming a
66+enqueue c !a = atomically (enqueueSTM c a)
67+
68+-- | Variant of enqueue for use in the STM monad.
69+enqueueSTM :: CQueue a -> a -> STM ()
70+enqueueSTM (CQueue _arrived incoming size) !a = do
71+ writeTChan incoming a
72+ modifyTVar' size succ
5673
5774data BlockSpec =
5875 NonBlocking
5976 | Blocking
6077 | Timeout Int
6178
79+-- Match operations
80+--
81+-- They can be either a message match or a channel match.
6282data MatchOn m a
6383 = MatchMsg (m -> Maybe a)
6484 | MatchChan (STM a)
85+ deriving (Functor)
6586
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.
6692type MatchChunks m a = [Either [m -> Maybe a] [STM a]]
6793
94+-- Splits a list of matches into chunks.
95+--
96+-- > concatMap (either (map MatchMsg) (map MatchChan)) . chunkMatches == id
97+--
6898chunkMatches :: [MatchOn m a] -> MatchChunks m a
6999chunkMatches [] = []
70100chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest
@@ -72,6 +102,7 @@ chunkMatches (MatchMsg m : ms) = Left (m : chk) : chunkMatches rest
72102chunkMatches (MatchChan r : ms) = Right (r : chk) : chunkMatches rest
73103 where (chk, rest) = spanMatchChan ms
74104
105+-- | @spanMatchMsg = first (map (\(MatchMsg x) -> x)) . span isMatchMsg@
75106spanMatchMsg :: [MatchOn m a] -> ([m -> Maybe a], [MatchOn m a])
76107spanMatchMsg [] = ([],[])
77108spanMatchMsg (m : ms)
@@ -79,6 +110,7 @@ spanMatchMsg (m : ms)
79110 | otherwise = ([], m:ms)
80111 where !(msgs,rest) = spanMatchMsg ms
81112
113+-- | @spanMatchMsg = first (map (\(MatchChan x) -> x)) . span isMatchChan@
82114spanMatchChan :: [MatchOn m a] -> ([STM a], [MatchOn m a])
83115spanMatchChan [] = ([],[])
84116spanMatchChan (m : ms)
@@ -95,7 +127,7 @@ dequeue :: forall m a.
95127 -> BlockSpec -- ^ Blocking behaviour
96128 -> [MatchOn m a] -- ^ List of matches
97129 -> IO (Maybe a) -- ^ 'Nothing' only on timeout
98−dequeue (CQueue arrived incoming) blockSpec matchons =
130+dequeue (CQueue arrived incoming size) blockSpec matchons = mask_ $ decrementJust $
99131 case blockSpec of
100132 Timeout n -> timeout n $ fmap fromJust run
101133 _other ->
@@ -107,9 +139,17 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
107139 -- no onException needed
108140 _other -> run
109141 where
142+ -- Decrement counter is smth is returned from the queue,
143+ -- this is safe to use as method is called under a mask
144+ -- and there is no 'unmasked' operation inside
145+ decrementJust :: IO (Maybe (Either a a)) -> IO (Maybe a)
146+ decrementJust f =
147+ traverse (either return (\x -> decrement >> return x)) =<< f
148+ decrement = atomically $ modifyTVar' size pred
149+
110150 chunks = chunkMatches matchons
111151
112− run = mask_ $ do
152+ run = do
113153 arr <- takeMVar arrived
114154 let grabNew xs = do
115155 r <- atomically $ tryReadTChan incoming
@@ -119,17 +159,25 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
119159 arr' <- grabNew arr
120160 goCheck chunks arr'
121161
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))
122166 waitChans ports on_block =
123− foldr orElse on_block (map (fmap Just) ports)
167+ foldr orElse on_block (map (fmap (Just . Left)) ports)
124168
125169 --
126170 -- First check the MatchChunks against the messages already in the
127171 -- mailbox. For channel matches, we do a non-blocking check at
128172 -- this point.
129173 --
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+ --
130178 goCheck :: MatchChunks m a
131179 -> StrictList m -- messages to check, in this order
132− -> IO (Maybe a)
180+ -> IO (Maybe (Either a a))
133181
134182 goCheck [] old = goWait old
135183
@@ -146,7 +194,7 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
146194 -- of passing around restore and setting up exception handlers is
147195 -- high. So just don't use expensive matchIfs!
148196 case checkArrived matches old of
149− (old', Just r) -> returnOld old' (Just r)
197+ (old', Just r) -> returnOld old' (Just (Right r))
150198 (old', Nothing) -> goCheck rest old'
151199 -- use the result list, which is now left-biased
152200
@@ -175,6 +223,7 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
175223 -- Contents of 'arrived' from now on is (old ++ new), and
176224 -- messages that arrive are snocced onto new.
177225 --
226+ goWait :: StrictList m -> IO (Maybe (Either a a))
178227 goWait old = do
179228 r <- waitIncoming `onException` putMVar arrived old
180229 case r of
@@ -192,7 +241,7 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
192241 --
193242 -- Right => message arrived on a channel first
194243 --
195− Right a -> returnOld old (Just a)
244+ Right a -> returnOld old (Just (Left a))
196245
197246 --
198247 -- A message arrived in the process inbox; check the MatchChunks for
@@ -201,7 +250,7 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
201250 goCheck1 :: MatchChunks m a
202251 -> m -- single message to check
203252 -> StrictList m -- old messages we have already checked
204− -> IO (Maybe a)
253+ -> IO (Maybe (Either a a))
205254
206255 goCheck1 [] m old = goWait (Snoc old m)
207256
@@ -214,16 +263,17 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
214263 goCheck1 (Left matches : rest) m old = do
215264 case checkMatches matches m of
216265 Nothing -> goCheck1 rest m old
217− Just p -> returnOld old (Just p)
266+ Just p -> returnOld old (Just (Right p))
218267
219268 -- a common pattern for putting back the arrived queue at the end
220− returnOld :: StrictList m -> Maybe a -> IO (Maybe a)
269+ returnOld :: StrictList m -> Maybe (Either a a) -> IO (Maybe (Either a a))
221270 returnOld old r = do putMVar arrived old; return r
222271
223272 -- as a side-effect, this left-biases the list
224273 checkArrived :: [m -> Maybe a] -> StrictList m -> (StrictList m, Maybe a)
225274 checkArrived matches list = go list Nil
226275 where
276+ -- @go xs ys@ searches for a message match in @append xs ys@
227277 go Nil Nil = (Nil, Nothing)
228278 go Nil r = go r Nil
229279 go (Append xs ys) tl = go xs (append ys tl)
@@ -239,5 +289,12 @@ dequeue (CQueue arrived incoming) blockSpec matchons =
239289
240290-- | Weak reference to a CQueue
241291mkWeakCQueue :: CQueue a -> IO () -> IO (Weak (CQueue a))
242−mkWeakCQueue m@(CQueue (StrictMVar (MVar m#)) _) f = IO $ \s ->
292+mkWeakCQueue m@(CQueue (StrictMVar (MVar m#)) _ _) f = IO $ \s ->
293+#if MIN_VERSION_base(4,9,0)
294+ case mkWeak# m# m (unIO f) s of (# s1, w #) -> (# s1, Weak w #)
295+#else
243296 case mkWeak# m# m f s of (# s1, w #) -> (# s1, Weak w #)
297+#endif
298+
299+queueSize :: CQueue a -> IO Int
300+queueSize (CQueue _ _ size) = readTVarIO size
src/Control/Distributed/Process/Internal/Closure/BuiltIn.hs+48−5
@@ -1,3 +1,5 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+{-# LANGUAGE RankNTypes #-}
13module Control.Distributed.Process.Internal.Closure.BuiltIn
24 ( -- * Remote table
35 remoteTable
@@ -6,6 +8,8 @@ module Control.Distributed.Process.Internal.Closure.BuiltIn
68 , sdictUnit
79 , sdictProcessId
810 , sdictSendPort
11+ , sdictStatic
12+ , sdictClosure
913 -- * Some static values
1014 , sndStatic
1115 -- * The CP type and associated combinators
@@ -16,13 +20,16 @@ module Control.Distributed.Process.Internal.Closure.BuiltIn
1620 , bindCP
1721 , seqCP
1822 -- * CP versions of Cloud Haskell primitives
23+ , decodeProcessIdStatic
1924 , cpLink
2025 , cpUnlink
26+ , cpRelay
2127 , cpSend
2228 , cpExpect
2329 , cpNewChan
2430 -- * Support for some CH operations
25− , cpDelay
31+ , cpDelayed
32+ , cpEnableTraceRemote
2633 ) where
2734
2835import Data.ByteString.Lazy (ByteString)
@@ -45,6 +52,7 @@ import Control.Distributed.Static
4552import Control.Distributed.Process.Serializable
4653 ( SerializableDict(..)
4754 , Serializable
55+ , TypeableDict(..)
4856 )
4957import Control.Distributed.Process.Internal.Types
5058 ( Process
@@ -56,6 +64,7 @@ import Control.Distributed.Process.Internal.Types
5664import Control.Distributed.Process.Internal.Primitives
5765 ( link
5866 , unlink
67+ , relay
5968 , send
6069 , expect
6170 , newChan
@@ -76,12 +85,15 @@ remoteTable =
7685 . registerStatic "$sdictUnit" (toDynamic (SerializableDict :: SerializableDict ()))
7786 . registerStatic "$sdictProcessId" (toDynamic (SerializableDict :: SerializableDict ProcessId))
7887 . registerStatic "$sdictSendPort_" (toDynamic (sdictSendPort_ :: SerializableDict ANY -> SerializableDict (SendPort ANY)))
88+ . registerStatic "$sdictStatic" (toDynamic (sdictStatic_ :: TypeableDict ANY -> SerializableDict (Static ANY)))
89+ . registerStatic "$sdictClosure" (toDynamic (sdictClosure_ :: TypeableDict ANY -> SerializableDict (Closure ANY)))
7990 . registerStatic "$returnProcess" (toDynamic (return :: ANY -> Process ANY))
8091 . registerStatic "$seqProcess" (toDynamic ((>>) :: Process ANY1 -> Process ANY2 -> Process ANY2))
8192 . registerStatic "$bindProcess" (toDynamic ((>>=) :: Process ANY1 -> (ANY1 -> Process ANY2) -> Process ANY2))
8293 . registerStatic "$decodeProcessId" (toDynamic (decode :: ByteString -> ProcessId))
8394 . registerStatic "$link" (toDynamic link)
8495 . registerStatic "$unlink" (toDynamic unlink)
96+ . registerStatic "$relay" (toDynamic relay)
8597 . registerStatic "$sendDict" (toDynamic (sendDict :: SerializableDict ANY -> ProcessId -> ANY -> Process ()))
8698 . registerStatic "$expectDict" (toDynamic (expectDict :: SerializableDict ANY -> Process ANY))
8799 . registerStatic "$newChanDict" (toDynamic (newChanDict :: SerializableDict ANY -> Process (SendPort ANY, ReceivePort ANY)))
@@ -95,6 +107,12 @@ remoteTable =
95107 sdictSendPort_ :: forall a. SerializableDict a -> SerializableDict (SendPort a)
96108 sdictSendPort_ SerializableDict = SerializableDict
97109
110+ sdictStatic_ :: forall a. TypeableDict a -> SerializableDict (Static a)
111+ sdictStatic_ TypeableDict = SerializableDict
112+
113+ sdictClosure_ :: forall a. TypeableDict a -> SerializableDict (Closure a)
114+ sdictClosure_ TypeableDict = SerializableDict
115+
98116 sendDict :: forall a. SerializableDict a -> ProcessId -> a -> Process ()
99117 sendDict SerializableDict = send
100118
@@ -137,6 +155,14 @@ sdictSendPort :: Typeable a
137155 => Static (SerializableDict a) -> Static (SerializableDict (SendPort a))
138156sdictSendPort = staticApply (staticLabel "$sdictSendPort_")
139157
158+-- | Serialization dictionary for 'Static'.
159+sdictStatic :: Typeable a => Static (TypeableDict a) -> Static (SerializableDict (Static a))
160+sdictStatic = staticApply (staticLabel "$sdictStatic")
161+
162+-- | Serialization dictionary for 'Closure'.
163+sdictClosure :: Typeable a => Static (TypeableDict a) -> Static (SerializableDict (Closure a))
164+sdictClosure = staticApply (staticLabel "$sdictClosure")
165+
140166--------------------------------------------------------------------------------
141167-- Static values --
142168--------------------------------------------------------------------------------
@@ -246,6 +272,22 @@ cpNewChan dict = staticClosure (newChanDictStatic `staticApply` dict)
246272 => Static (SerializableDict a -> Process (SendPort a, ReceivePort a))
247273 newChanDictStatic = staticLabel "$newChanDict"
248274
275+-- | 'CP' version of 'relay'
276+cpRelay :: ProcessId -> Closure (Process ())
277+cpRelay = closure (relayStatic `staticCompose` decodeProcessIdStatic) . encode
278+ where
279+ relayStatic :: Static (ProcessId -> Process ())
280+ relayStatic = staticLabel "$relay"
281+
282+-- TODO: move cpEnableTraceRemote into Trace/Primitives.hs
283+
284+cpEnableTraceRemote :: ProcessId -> Closure (Process ())
285+cpEnableTraceRemote =
286+ closure (enableTraceStatic `staticCompose` decodeProcessIdStatic) . encode
287+ where
288+ enableTraceStatic :: Static (ProcessId -> Process ())
289+ enableTraceStatic = staticLabel "$enableTraceRemote"
290+
249291--------------------------------------------------------------------------------
250292-- Support for spawn --
251293--------------------------------------------------------------------------------
@@ -253,19 +295,20 @@ cpNewChan dict = staticClosure (newChanDictStatic `staticApply` dict)
253295-- | @delay them p@ is a process that waits for a signal (a message of type @()@)
254296-- from 'them' (origin is not verified) before proceeding as @p@. In order to
255297-- avoid waiting forever, @delay them p@ monitors 'them'. If it receives a
256−-- monitor message instead it simply terminates.
298+-- monitor message instead, it proceeds as @p@ too.
257299delay :: ProcessId -> Process () -> Process ()
258300delay them p = do
259301 ref <- monitor them
260302 let sameRef (ProcessMonitorNotification ref' _ _) = ref == ref'
261303 receiveWait [
262− match $ \() -> unmonitor ref >> p
304+ match $ \() -> unmonitor ref
263305 , matchIf sameRef $ \_ -> return ()
264306 ]
307+ p
265308
266309-- | 'CP' version of 'delay'
267−cpDelay :: ProcessId -> Closure (Process ()) -> Closure (Process ())
268−cpDelay = closureApply . cpDelay'
310+cpDelayed :: ProcessId -> Closure (Process ()) -> Closure (Process ())
311+cpDelayed = closureApply . cpDelay'
269312 where
270313 cpDelay' :: ProcessId -> Closure (Process () -> Process ())
271314 cpDelay' pid = closure decoder (encode pid)
src/Control/Distributed/Process/Internal/Closure/Explicit.hsnew file+147
@@ -0,0 +1,147 @@
1+{-# LANGUAGE ScopedTypeVariables
2+ , MultiParamTypeClasses
3+ , FlexibleInstances
4+ , FunctionalDependencies
5+ , FlexibleContexts
6+ , UndecidableInstances
7+ , KindSignatures
8+ , GADTs
9+ , EmptyDataDecls
10+ , DeriveDataTypeable #-}
11+module Control.Distributed.Process.Internal.Closure.Explicit
12+ (
13+ RemoteRegister
14+ , MkTDict(..)
15+ , mkStaticVal
16+ , mkClosureValSingle
17+ , mkClosureVal
18+ , call'
19+ ) where
20+
21+import Control.Distributed.Static
22+import Control.Distributed.Process.Serializable
23+import Control.Distributed.Process.Internal.Closure.BuiltIn
24+ ( -- Static dictionaries and associated operations
25+ staticDecode
26+ )
27+import Control.Distributed.Process
28+import Data.Rank1Dynamic
29+import Data.Rank1Typeable
30+import Data.Binary(encode,put,get,Binary)
31+import qualified Data.ByteString.Lazy as B
32+
33+-- | A RemoteRegister is a trasformer on a RemoteTable to register additional static values.
34+type RemoteRegister = RemoteTable -> RemoteTable
35+
36+-- | This takes an explicit name and a value, and produces both a static reference to the name and a RemoteRegister for it.
37+mkStaticVal :: Serializable a => String -> a -> (Static a, RemoteRegister)
38+mkStaticVal n v = (staticLabel n_s, registerStatic n_s (toDynamic v))
39+ where n_s = n
40+
41+class MkTDict a where
42+ mkTDict :: String -> a -> RemoteRegister
43+
44+instance (Serializable b) => MkTDict (Process b) where
45+ mkTDict _ _ = registerStatic (show (typeOf (undefined :: b)) ++ "__staticDict") (toDynamic (SerializableDict :: SerializableDict b))
46+
47+instance MkTDict a where
48+ mkTDict _ _ = id
49+
50+-- | This takes an explicit name, a function of arity one, and creates a creates a function yielding a closure and a remote register for it.
51+mkClosureValSingle :: forall a b. (Serializable a, Typeable b, MkTDict b) => String -> (a -> b) -> (a -> Closure b, RemoteRegister)
52+mkClosureValSingle n v = (c, registerStatic n_s (toDynamic v) .
53+ registerStatic n_sdict (toDynamic sdict) .
54+ mkTDict n_tdict (undefined :: b)
55+ ) where
56+ n_s = n
57+ n_sdict = n ++ "__sdict"
58+ n_tdict = n ++ "__tdict"
59+
60+ c = closure decoder . encode
61+
62+ decoder = (staticLabel n_s :: Static (a -> b)) `staticCompose` staticDecode (staticLabel n_sdict :: Static (SerializableDict a))
63+
64+ sdict :: (SerializableDict a)
65+ sdict = SerializableDict
66+
67+-- | This takes an explict name, a function of any arity, and creates a function yielding a closure and a remote register for it.
68+mkClosureVal :: forall func argTuple result closureFunction.
69+ (Curry (argTuple -> Closure result) closureFunction,
70+ MkTDict result,
71+ Uncurry HTrue argTuple func result,
72+ Typeable result, Serializable argTuple, IsFunction func HTrue) =>
73+ String -> func -> (closureFunction, RemoteRegister)
74+mkClosureVal n v = (curryFun c, rtable)
75+ where
76+ uv :: argTuple -> result
77+ uv = uncurry' reify v
78+
79+ n_s = n
80+ n_sdict = n ++ "__sdict"
81+ n_tdict = n ++ "__tdict"
82+
83+ c :: argTuple -> Closure result
84+ c = closure decoder . encode
85+
86+ decoder :: Static (B.ByteString -> result)
87+ decoder = (staticLabel n_s :: Static (argTuple -> result)) `staticCompose` staticDecode (staticLabel n_sdict :: Static (SerializableDict argTuple))
88+
89+ rtable = registerStatic n_s (toDynamic uv) .
90+ registerStatic n_sdict (toDynamic sdict) .
91+ mkTDict n_tdict (undefined :: result)
92+
93+
94+ sdict :: (SerializableDict argTuple)
95+ sdict = SerializableDict
96+
97+-- | Works just like standard call, but with a simpler signature.
98+call' :: forall a. Serializable a => NodeId -> Closure (Process a) -> Process a
99+call' = call (staticLabel $ (show $ typeOf $ (undefined :: a)) ++ "__staticDict")
100+
101+
102+data EndOfTuple deriving Typeable
103+instance Binary EndOfTuple where
104+ put _ = return ()
105+ get = return undefined
106+
107+-- This generic curry is straightforward
108+class Curry a b | a -> b where
109+ curryFun :: a -> b
110+
111+instance Curry ((a, EndOfTuple) -> b) (a -> b) where
112+ curryFun f = \x -> f (x,undefined)
113+
114+instance Curry (b -> c) r => Curry ((a,b) -> c) (a -> r) where
115+ curryFun f = \x -> curryFun (\y -> (f (x,y)))
116+
117+
118+-- This generic uncurry courtesy Andrea Vezzosi
119+data HTrue
120+data HFalse
121+data Fun :: * -> * -> * -> * where
122+ Done :: Fun EndOfTuple r r
123+ Moar :: Fun xs f r -> Fun (x,xs) (x -> f) r
124+
125+class Uncurry'' args func result | func -> args, func -> result, args result -> func where
126+ reify :: Fun args func result
127+
128+class Uncurry flag args func result | flag func -> args, flag func -> result, args result -> func where
129+ reify' :: flag -> Fun args func result
130+
131+instance Uncurry'' rest f r => Uncurry HTrue (a,rest) (a -> f) r where
132+ reify' _ = Moar reify
133+
134+instance Uncurry HFalse EndOfTuple a a where
135+ reify' _ = Done
136+
137+instance (IsFunction func b, Uncurry b args func result) => Uncurry'' args func result where
138+ reify = reify' (undefined :: b)
139+
140+uncurry' :: Fun args func result -> func -> args -> result
141+uncurry' Done r _ = r
142+uncurry' (Moar fun) f (x,xs) = uncurry' fun (f x) xs
143+
144+class IsFunction t b | t -> b
145+instance (b ~ HTrue) => IsFunction (a -> c) b
146+instance (b ~ HFalse) => IsFunction a b
147+
src/Control/Distributed/Process/Internal/Closure/TH.hs+24−8
@@ -1,5 +1,5 @@
11-- | Template Haskell support
2−{-# LANGUAGE TemplateHaskell #-}
2+{-# LANGUAGE TemplateHaskell, CPP #-}
33module Control.Distributed.Process.Internal.Closure.TH
44 ( -- * User-level API
55 remotable
@@ -29,7 +29,14 @@ import Language.Haskell.TH
2929 , Type(AppT, ForallT, VarT, ArrowT)
3030 , Info(VarI)
3131 , TyVarBndr(PlainTV, KindedTV)
32− , Pred(ClassP)
32+ , Pred
33+#if MIN_VERSION_template_haskell(2,10,0)
34+ , conT
35+ , appT
36+#else
37+ , classP
38+#endif
39+ , varT
3340 -- Lifted constructors
3441 -- .. Literals
3542 , stringL
@@ -244,16 +251,21 @@ generateStatic :: Name -> [TyVarBndr] -> Type -> Q [Dec]
244251generateStatic n xs typ = do
245252 staticTyp <- [t| Static |]
246253 sequence
247− [ sigD (staticName n) $
254+ [ sigD (staticName n) $ do
255+ txs <- sequence $ map typeable xs
248256 return (ForallT xs
249− (map typeable xs)
250− (staticTyp `AppT` typ)
251− )
257+ txs
258+ (staticTyp `AppT` typ))
252259 , sfnD (staticName n) [| staticLabel $(showFQN n) |]
253260 ]
254261 where
255− typeable :: TyVarBndr -> Pred
256− typeable tv = ClassP (mkName "Typeable") [VarT (tyVarBndrName tv)]
262+ typeable :: TyVarBndr -> Q Pred
263+ typeable tv =
264+#if MIN_VERSION_template_haskell(2,10,0)
265+ conT (mkName "Typeable") `appT` varT (tyVarBndrName tv)
266+#else
267+ classP (mkName "Typeable") [varT (tyVarBndrName tv)]
268+#endif
257269
258270-- | Generate a serialization dictionary with name 'n' for type 'typ'
259271generateDict :: Name -> Type -> Q [Dec]
@@ -291,7 +303,11 @@ getType :: Name -> Q (Name, Type)
291303getType name = do
292304 info <- reify name
293305 case info of
306+#if MIN_VERSION_template_haskell(2,11,0)
307+ VarI origName typ _ -> return (origName, typ)
308+#else
294309 VarI origName typ _ _ -> return (origName, typ)
310+#endif
295311 _ -> fail $ show name ++ " not found"
296312
297313-- | Variation on 'funD' which takes a single expression to define the function
src/Control/Distributed/Process/Internal/Messaging.hs+63−25
@@ -5,6 +5,7 @@ module Control.Distributed.Process.Internal.Messaging
55 , disconnect
66 , closeImplicitReconnections
77 , impliesDeathOf
8+ , sendCtrlMsg
89 ) where
910
1011import Data.Accessor ((^.), (^=))
@@ -12,9 +13,14 @@ import Data.Binary (Binary, encode)
1213import qualified Data.Map as Map (partitionWithKey, elems)
1314import qualified Data.ByteString.Lazy as BSL (toChunks)
1415import qualified Data.ByteString as BSS (ByteString)
15−import Control.Distributed.Process.Internal.StrictMVar (withMVar, modifyMVar_)
16+import Control.Distributed.Process.Serializable ()
17+
18+import Control.Concurrent (forkIO)
1619import Control.Concurrent.Chan (writeChan)
20+import Control.Exception (mask_)
1721import Control.Monad (unless)
22+import Control.Monad.IO.Class (liftIO)
23+import Control.Monad.Reader (ask)
1824import qualified Network.Transport as NT
1925 ( Connection
2026 , send
@@ -24,7 +30,10 @@ import qualified Network.Transport as NT
2430 , close
2531 )
2632import Control.Distributed.Process.Internal.Types
27− ( LocalNode(localState, localEndPoint, localCtrlChan)
33+ ( LocalNode(localEndPoint, localCtrlChan)
34+ , withValidLocalState
35+ , modifyValidLocalState
36+ , modifyValidLocalState_
2837 , Identifier
2938 , localConnections
3039 , localConnectionBetween
@@ -36,12 +45,16 @@ import Control.Distributed.Process.Internal.Types
3645 , ProcessSignal(Died)
3746 , DiedReason(DiedDisconnect)
3847 , ImplicitReconnect(WithImplicitReconnect)
39− , NodeId
40− , ProcessId(processNodeId)
48+ , NodeId(..)
49+ , ProcessId(..)
50+ , LocalNode(..)
51+ , LocalProcess(..)
52+ , Process(..)
4153 , SendPortId(sendPortProcessId)
4254 , Identifier(NodeIdentifier, ProcessIdentifier, SendPortIdentifier)
4355 )
4456import Control.Distributed.Process.Serializable (Serializable)
57+import Data.Foldable (forM_)
4558
4659--------------------------------------------------------------------------------
4760-- Message sending --
@@ -65,7 +78,7 @@ sendPayload node from to implicitReconnect payload = do
6578 unless didSend $ do
6679 writeChan (localCtrlChan node) NCMsg
6780 { ctrlMsgSender = to
68− , ctrlMsgSignal = Died to DiedDisconnect
81+ , ctrlMsgSignal = Died (NodeIdentifier $ nodeOf to) DiedDisconnect
6982 }
7083
7184sendBinary :: Binary a
@@ -94,7 +107,7 @@ setupConnBetween :: LocalNode
94107 -> ImplicitReconnect
95108 -> IO (Maybe NT.Connection)
96109setupConnBetween node from to implicitReconnect = do
97− mConn <- NT.connect endPoint
110+ mConn <- NT.connect (localEndPoint node)
98111 (nodeAddress . nodeOf $ to)
99112 NT.ReliableOrdered
100113 NT.defaultConnectHints
@@ -105,14 +118,11 @@ setupConnBetween node from to implicitReconnect = do
105118 Left _ ->
106119 return Nothing
107120 Right () -> do
108− modifyMVar_ nodeState $ return .
109− (localConnectionBetween from to ^= Just (conn, implicitReconnect))
121+ modifyValidLocalState_ node $
122+ return . (localConnectionBetween from to ^= Just (conn, implicitReconnect))
110123 return $ Just conn
111124 Left _ ->
112125 return Nothing
113− where
114− endPoint = localEndPoint node
115− nodeState = localState node
116126
117127connBetween :: LocalNode
118128 -> Identifier
@@ -120,33 +130,37 @@ connBetween :: LocalNode
120130 -> ImplicitReconnect
121131 -> IO (Maybe NT.Connection)
122132connBetween node from to implicitReconnect = do
123− mConn <- withMVar nodeState $ return . (^. localConnectionBetween from to)
133+ mConn <- withValidLocalState node $
134+ return . (^. localConnectionBetween from to)
124135 case mConn of
125136 Just (conn, _) ->
126137 return $ Just conn
127138 Nothing ->
128139 setupConnBetween node from to implicitReconnect
129− where
130− nodeState = localState node
131140
132141disconnect :: LocalNode -> Identifier -> Identifier -> IO ()
133−disconnect node from to =
134− modifyMVar_ (localState node) $ \st ->
135− case st ^. localConnectionBetween from to of
142+disconnect node from to = mask_ $ do
143+ mio <- modifyValidLocalState node $ \vst ->
144+ case vst ^. localConnectionBetween from to of
136145 Nothing ->
137− return st
146+ return (vst, return ())
138147 Just (conn, _) -> do
139− NT.close conn
140− return (localConnectionBetween from to ^= Nothing $ st)
148+ return ( localConnectionBetween from to ^= Nothing $ vst
149+ , NT.close conn
150+ )
151+ forM_ mio forkIO
141152
142153closeImplicitReconnections :: LocalNode -> Identifier -> IO ()
143−closeImplicitReconnections node to =
144− modifyMVar_ (localState node) $ \st -> do
154+closeImplicitReconnections node to = mask_ $ do
155+ mconns <- modifyValidLocalState node $ \vst -> do
145156 let shouldClose (_, to') (_, WithImplicitReconnect) = to `impliesDeathOf` to'
146157 shouldClose _ _ = False
147− let (affected, unaffected) = Map.partitionWithKey shouldClose (st ^. localConnections)
148− mapM_ (NT.close . fst) (Map.elems affected)
149− return (localConnections ^= unaffected $ st)
158+ let (affected, unaffected) =
159+ Map.partitionWithKey shouldClose (vst ^. localConnections)
160+ return ( localConnections ^= unaffected $ vst
161+ , map fst $ Map.elems affected
162+ )
163+ forM_ mconns $ forkIO . mapM_ NT.close
150164
151165-- | @a `impliesDeathOf` b@ is true if the death of @a@ (for instance, a node)
152166-- implies the death of @b@ (for instance, a process on that node)
@@ -167,3 +181,27 @@ SendPortIdentifier cid `impliesDeathOf` SendPortIdentifier cid' =
167181 cid' == cid
168182_ `impliesDeathOf` _ =
169183 False
184+
185+
186+-- Send a control message. Evaluates the message to HNF before sending it.
187+--
188+-- The message shouldn't produce more errors when further evaluated. If
189+-- evaluation threw errors the node controller or the receiver would crash when
190+-- inspecting it.
191+sendCtrlMsg :: Maybe NodeId -- ^ Nothing for the local node
192+ -> ProcessSignal -- ^ Message to send
193+ -> Process ()
194+sendCtrlMsg mNid signal = do
195+ proc <- ask
196+ let msg = NCMsg { ctrlMsgSender = ProcessIdentifier (processId proc)
197+ , ctrlMsgSignal = signal
198+ }
199+ case mNid of
200+ Nothing -> do
201+ liftIO $ writeChan (localCtrlChan (processNode proc)) $! msg
202+ Just nid ->
203+ liftIO $ sendBinary (processNode proc)
204+ (NodeIdentifier (processNodeId $ processId proc))
205+ (NodeIdentifier nid)
206+ WithImplicitReconnect
207+ msg
src/Control/Distributed/Process/Internal/Primitives.hs+473−257
@@ -1,3 +1,11 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE RankNTypes #-}
3+{-# LANGUAGE DeriveDataTypeable #-}
4+{-# LANGUAGE DeriveFunctor #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+{-# LANGUAGE ExistentialQuantification #-}
7+{-# LANGUAGE BangPatterns #-}
8+
19-- | Cloud Haskell primitives
210--
311-- We define these in a separate module so that we don't have to rely on
@@ -5,6 +13,7 @@
513module Control.Distributed.Process.Internal.Primitives
614 ( -- * Basic messaging
715 send
16+ , usend
817 , expect
918 -- * Channels
1019 , newChan
@@ -12,6 +21,12 @@ module Control.Distributed.Process.Internal.Primitives
1221 , receiveChan
1322 , mergePortsBiased
1423 , mergePortsRR
24+ -- * Unsafe messaging variants
25+ , unsafeSend
26+ , unsafeUSend
27+ , unsafeSendChan
28+ , unsafeNSend
29+ , unsafeNSendRemote
1530 -- * Advanced messaging
1631 , Match
1732 , receiveWait
@@ -22,12 +37,22 @@ module Control.Distributed.Process.Internal.Primitives
2237 , matchAny
2338 , matchAnyIf
2439 , matchChan
40+ , matchSTM
2541 , matchMessage
2642 , matchMessageIf
43+ , isEncoded
2744 , wrapMessage
45+ , unsafeWrapMessage
2846 , unwrapMessage
2947 , handleMessage
48+ , handleMessageIf
49+ , handleMessage_
50+ , handleMessageIf_
3051 , forward
52+ , uforward
53+ , delegate
54+ , relay
55+ , proxy
3156 -- * Process management
3257 , terminate
3358 , ProcessTerminationException(..)
@@ -39,18 +64,23 @@ module Control.Distributed.Process.Internal.Primitives
3964 -- keep the exception constructor hidden, so that handling exit
4065 -- reasons /must/ take place via the 'catchExit' family of primitives
4166 , ProcessExitException()
42− , exitSource
4367 , getSelfPid
4468 , getSelfNode
4569 , ProcessInfo(..)
4670 , getProcessInfo
71+ , NodeStats(..)
72+ , getNodeStats
73+ , getLocalNodeStats
4774 -- * Monitoring and linking
4875 , link
4976 , unlink
5077 , monitor
5178 , unmonitor
79+ , unmonitorAsync
5280 , withMonitor
81+ , withMonitor_
5382 -- * Logging
83+ , SayMessage(..)
5484 , say
5585 -- * Registry
5686 , register
@@ -72,6 +102,7 @@ module Control.Distributed.Process.Internal.Primitives
72102 , catches
73103 , try
74104 , mask
105+ , mask_
75106 , onException
76107 , bracket
77108 , bracket_
@@ -89,27 +120,40 @@ module Control.Distributed.Process.Internal.Primitives
89120 -- * Reconnecting
90121 , reconnect
91122 , reconnectPort
92− -- * Tracing/Debugging
93− , trace
123+ -- * Internal Exports
124+ , sendCtrlMsg
94125 ) where
95126
96127#if ! MIN_VERSION_base(4,6,0)
97128import Prelude hiding (catch)
98129#endif
99130
100−import Control.Monad (when)
131+import Data.Binary (Binary(..), Put, Get, decode)
132+import Data.Time.Clock (getCurrentTime, UTCTime(..))
133+import Data.Time.Calendar (Day(..))
134+import Data.Time.Format (formatTime)
135+#if MIN_VERSION_time(1,5,0)
136+import Data.Time.Format (defaultTimeLocale)
137+#else
138+import System.Locale (defaultTimeLocale)
139+#endif
140+import System.Timeout (timeout)
141+import Control.Monad (when, void)
101142import Control.Monad.Reader (ask)
102−import Control.Monad.IO.Class (MonadIO, liftIO)
103−import Control.Applicative ((<$>))
104−import Control.Exception (Exception(..), throw, throwIO, SomeException)
105−import qualified Control.Exception as Ex (catch, mask, try)
143+import Control.Monad.IO.Class (liftIO)
144+import Control.Monad.Catch
145+ ( Exception
146+ , SomeException
147+ , throwM
148+ , fromException
149+ )
150+import qualified Control.Monad.Catch as Catch
151+import Control.Applicative
106152import Control.Distributed.Process.Internal.StrictMVar
107153 ( StrictMVar
108154 , modifyMVar
109155 , modifyMVar_
110− , withMVar
111156 )
112−import Control.Concurrent.Chan (writeChan)
113157import Control.Concurrent.STM
114158 ( STM
115159 , TVar
@@ -121,13 +165,18 @@ import Control.Concurrent.STM
121165 )
122166import Control.Distributed.Process.Internal.CQueue
123167 ( dequeue
124− , enqueue
125168 , BlockSpec(..)
126169 , MatchOn(..)
127170 )
128171import Control.Distributed.Process.Serializable (Serializable, fingerprint)
129−import Control.Distributed.Static (Closure, Static)
172+import Data.Accessor ((^.), (^:), (^=))
173+import Control.Distributed.Static
174+ ( Static
175+ , Closure
176+ )
177+import Data.Rank1Typeable (Typeable)
130178import qualified Control.Distributed.Static as Static (unstatic, unclosure)
179+import qualified Control.Distributed.Process.UnsafePrimitives as Unsafe
131180import Control.Distributed.Process.Internal.Types
132181 ( NodeId(..)
133182 , ProcessId(..)
@@ -137,20 +186,20 @@ import Control.Distributed.Process.Internal.Types
137186 , Message(..)
138187 , MonitorRef(..)
139188 , SpawnRef(..)
140− , NCMsg(..)
141189 , ProcessSignal(..)
190+ , NodeMonitorNotification(..)
191+ , ProcessMonitorNotification(..)
142192 , monitorCounter
143193 , spawnCounter
144194 , SendPort(..)
145195 , ReceivePort(..)
146196 , channelCounter
147197 , typedChannelWithId
148− , localProcessWithId
149198 , TypedChannel(..)
150199 , SendPortId(..)
151200 , Identifier(..)
152201 , ProcessExitException(..)
153− , exitSource
202+ , DiedReason(..)
154203 , DidUnmonitor(..)
155204 , DidUnlinkProcess(..)
156205 , DidUnlinkNode(..)
@@ -160,11 +209,12 @@ import Control.Distributed.Process.Internal.Types
160209 , ProcessRegistrationException(..)
161210 , ProcessInfo(..)
162211 , ProcessInfoNone(..)
212+ , NodeStats(..)
213+ , isEncoded
163214 , createMessage
164215 , createUnencodedMessage
165− , runLocalProcess
166− , ImplicitReconnect(WithImplicitReconnect, NoImplicitReconnect)
167− , LocalProcessState(..)
216+ , ImplicitReconnect( NoImplicitReconnect)
217+ , LocalProcessState
168218 , LocalSendPortId
169219 , messageToPayload
170220 )
@@ -173,24 +223,21 @@ import Control.Distributed.Process.Internal.Messaging
173223 , sendBinary
174224 , sendPayload
175225 , disconnect
226+ , sendCtrlMsg
227+ )
228+import Control.Distributed.Process.Management.Internal.Types
229+ ( MxEvent(..)
230+ )
231+import Control.Distributed.Process.Management.Internal.Trace.Types
232+ ( traceEvent
176233 )
177−import qualified Control.Distributed.Process.Internal.Trace as Trace
178234import Control.Distributed.Process.Internal.WeakTQueue
179− ( TQueue
180− , newTQueueIO
235+ ( newTQueueIO
181236 , readTQueue
182237 , mkWeakTQueue
183− , writeTQueue
184238 )
185−import Data.Accessor ((^.), (^:), (^=))
186−import Data.Binary (decode)
187−import Data.Foldable (forM_)
188−import Data.Rank1Typeable (Typeable)
189−import Data.Time.Clock (getCurrentTime)
190−import Data.Time.Format (formatTime)
191−import System.Locale (defaultTimeLocale)
192−import System.Mem.Weak (deRefWeak)
193−import System.Timeout (timeout)
239+import Prelude
240+
194241import Unsafe.Coerce
195242
196243--------------------------------------------------------------------------------
@@ -200,18 +247,57 @@ import Unsafe.Coerce
200247-- | Send a message
201248send :: Serializable a => ProcessId -> a -> Process ()
202249-- This requires a lookup on every send. If we want to avoid that we need to
203−-- modify serializable to allow for stateful (IO) deserialization
250+-- modify serializable to allow for stateful (IO) deserialization.
204251send them msg = do
205252 proc <- ask
206− let node = localNodeId (processNode proc)
253+ let us = processId proc
254+ node = processNode proc
255+ nodeId = localNodeId node
207256 destNode = (processNodeId them) in do
208− case destNode == node of
209− True -> sendLocal them msg
210− False -> liftIO $ sendMessage (processNode proc)
211− (ProcessIdentifier (processId proc))
212− (ProcessIdentifier them)
213− NoImplicitReconnect
214− msg
257+ liftIO $ traceEvent (localEventBus node)
258+ (MxSent them us (createUnencodedMessage msg))
259+ if destNode == nodeId
260+ then sendLocal them msg
261+ else liftIO $ sendMessage (processNode proc)
262+ (ProcessIdentifier (processId proc))
263+ (ProcessIdentifier them)
264+ NoImplicitReconnect
265+ msg
266+
267+-- | /Unsafe/ variant of 'send'. This function makes /no/ attempt to serialize
268+-- and (in the case when the destination process resides on the same local
269+-- node) therefore ensure that the payload is fully evaluated before it is
270+-- delivered.
271+unsafeSend :: Serializable a => ProcessId -> a -> Process ()
272+unsafeSend = Unsafe.send
273+
274+-- | Send a message unreliably.
275+--
276+-- Unlike 'send', this function is insensitive to 'reconnect'. It will
277+-- try to send the message regardless of the history of connection failures
278+-- between the nodes.
279+--
280+-- Message passing with 'usend' is ordered for a given sender and receiver
281+-- if the messages arrive at all.
282+--
283+usend :: Serializable a => ProcessId -> a -> Process ()
284+usend them msg = do
285+ proc <- ask
286+ let there = processNodeId them
287+ let (us, node) = (processId proc, processNode proc)
288+ let msg' = wrapMessage msg
289+ liftIO $ traceEvent (localEventBus node) (MxSent them us msg')
290+ if localNodeId (processNode proc) == there
291+ then sendLocal them msg
292+ else sendCtrlMsg (Just there) $ UnreliableSend (processLocalId them)
293+ (createMessage msg)
294+
295+-- | /Unsafe/ variant of 'usend'. This function makes /no/ attempt to serialize
296+-- the message when the destination process resides on the same local
297+-- node. Therefore, a local receiver would need to be prepared to cope with any
298+-- errors resulting from evaluation of the message.
299+unsafeUSend :: Serializable a => ProcessId -> a -> Process ()
300+unsafeUSend = Unsafe.usend
215301
216302-- | Wait for a message of a specific type
217303expect :: forall a. Serializable a => Process a
@@ -221,7 +307,13 @@ expect = receiveWait [match return]
221307-- Channels --
222308--------------------------------------------------------------------------------
223309
224−-- | 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+--
225317newChan :: Serializable a => Process (SendPort a, ReceivePort a)
226318newChan = do
227319 proc <- ask
@@ -249,17 +341,27 @@ newChan = do
249341sendChan :: Serializable a => SendPort a -> a -> Process ()
250342sendChan (SendPort cid) msg = do
251343 proc <- ask
252− let node = localNodeId (processNode proc)
253− destNode = processNodeId (sendPortProcessId cid) in do
254− case destNode == node of
344+ let node = processNode proc
345+ pid = processId proc
346+ us = localNodeId node
347+ them = processNodeId (sendPortProcessId cid) in do
348+ liftIO $ traceEvent (localEventBus node) (MxSentToPort pid cid $ wrapMessage msg)
349+ case them == us of
255350 True -> sendChanLocal cid msg
256351 False -> do
257− liftIO $ sendBinary (processNode proc)
258− (ProcessIdentifier (processId proc))
352+ liftIO $ sendBinary node
353+ (ProcessIdentifier pid)
259354 (SendPortIdentifier cid)
260355 NoImplicitReconnect
261356 msg
262357
358+-- | Send a message on a typed channel. This function makes /no/ attempt to
359+-- serialize and (in the case when the @ReceivePort@ resides on the same local
360+-- node) therefore ensure that the payload is fully evaluated before it is
361+-- delivered.
362+unsafeSendChan :: Serializable a => SendPort a -> a -> Process ()
363+unsafeSendChan = Unsafe.sendChan
364+
263365-- | Wait for a message on a typed channel
264366receiveChan :: Serializable a => ReceivePort a -> Process a
265367receiveChan = liftIO . atomically . receiveSTM
@@ -303,13 +405,17 @@ mergePortsRR = \ps -> do
303405
304406-- | Opaque type used in 'receiveWait' and 'receiveTimeout'
305407newtype Match b = Match { unMatch :: MatchOn Message (Process b) }
408+ deriving (Functor)
306409
307410-- | Test the matches in order against each message in the queue
308411receiveWait :: [Match b] -> Process b
309412receiveWait ms = do
310413 queue <- processQueue <$> ask
311− Just proc <- liftIO $ dequeue queue Blocking (map unMatch ms)
312− proc
414+ mProc <- liftIO $ dequeue queue Blocking (map unMatch ms)
415+ case mProc of
416+ Just proc' -> proc'
417+ Nothing -> die $ "System Invariant Violation: CQueue.hs returned `Nothing` "
418+ ++ "in the absence of a timeout value."
313419
314420-- | Like 'receiveWait' but with a timeout.
315421--
@@ -325,9 +431,26 @@ receiveTimeout t ms = do
325431 Nothing -> return Nothing
326432 Just proc -> Just <$> proc
327433
434+-- | Match on a typed channel
328435matchChan :: ReceivePort a -> (a -> Process b) -> Match b
329436matchChan p fn = Match $ MatchChan (fmap fn (receiveSTM p))
330437
438+-- | Match on an arbitrary STM action.
439+--
440+-- This rather unusaul /match primitive/ allows us to compose arbitrary STM
441+-- actions with checks against our process' mailbox and/or any typed channel
442+-- @ReceivePort@s we may hold.
443+--
444+-- This allows us to process multiple input streams /along with our mailbox/,
445+-- in just the same way that 'matchChan' supports checking both the mailbox
446+-- /and/ an arbitrary set of typed channels in one atomic transaction.
447+--
448+-- Note there are no ordering guarnatees with respect to these disparate input
449+-- sources.
450+--
451+matchSTM :: STM a -> (a -> Process b) -> Match b
452+matchSTM stm fn = Match $ MatchChan (fmap fn stm)
453+
331454-- | Match against any message of the right type
332455match :: forall a b. Serializable a => (a -> Process b) -> Match b
333456match = matchIf (const True)
@@ -367,33 +490,62 @@ matchMessageIf c p = Match $ MatchMsg $ \msg ->
367490forward :: Message -> ProcessId -> Process ()
368491forward msg them = do
369492 proc <- ask
370− let node = localNodeId (processNode proc)
493+ let node = processNode proc
494+ us = processId proc
495+ nid = localNodeId node
371496 destNode = (processNodeId them) in do
372− case destNode == node of
373− True -> sendCtrlMsg Nothing (LocalSend them msg)
374− False -> liftIO $ sendPayload (processNode proc)
375− (ProcessIdentifier (processId proc))
376− (ProcessIdentifier them)
377− NoImplicitReconnect
378− (messageToPayload msg)
497+ liftIO $ traceEvent (localEventBus node) (MxSent them us msg)
498+ if destNode == nid
499+ then sendCtrlMsg Nothing (LocalSend them msg)
500+ else liftIO $ sendPayload (processNode proc)
501+ (ProcessIdentifier (processId proc))
502+ (ProcessIdentifier them)
503+ NoImplicitReconnect
504+ (messageToPayload msg)
379505
506+-- | Forward a raw 'Message' to the given 'ProcessId'.
507+--
508+-- Unlike 'forward', this function is insensitive to 'reconnect'. It will
509+-- try to send the message regardless of the history of connection failures
510+-- between the nodes.
511+uforward :: Message -> ProcessId -> Process ()
512+uforward msg them = do
513+ proc <- ask
514+ let node = processNode proc
515+ us = processId proc
516+ nid = localNodeId node
517+ destNode = (processNodeId them) in do
518+ liftIO $ traceEvent (localEventBus node) (MxSent them us msg)
519+ if destNode == nid
520+ then sendCtrlMsg Nothing (LocalSend them msg)
521+ else sendCtrlMsg (Just destNode) $ UnreliableSend (processLocalId them) msg
380522
381523-- | Wrap a 'Serializable' value in a 'Message'. Note that 'Message's are
382−-- 'Serializable' - like the datum they contain - but remember that deserializing
383−-- a 'Message' will yield a 'Message', not the type within it! To obtain the
524+-- 'Serializable' - like the datum they contain - but also note, deserialising
525+-- such a 'Message' will yield a 'Message', not the type within it! To obtain the
384526-- wrapped datum, use 'unwrapMessage' or 'handleMessage' with a specific type.
385527--
386528-- @
387529-- do
388−-- self <- getSelfPid
389−-- send self (wrapMessage "blah")
390−-- Nothing <- expectTimeout 1000000 :: Process (Maybe String)
391−-- (Just m) <- expectTimeout 1000000 :: Process (Maybe Message)
392−-- (Just "blah") <- unwrapMessage m :: Process (Maybe String)
530+-- self <- getSelfPid
531+-- send self (wrapMessage "blah")
532+-- Nothing <- expectTimeout 1000000 :: Process (Maybe String)
533+-- (Just m) <- expectTimeout 1000000 :: Process (Maybe Message)
534+-- (Just "blah") <- unwrapMessage m :: Process (Maybe String)
393535-- @
394536--
395537wrapMessage :: Serializable a => a -> Message
396−wrapMessage = createMessage
538+wrapMessage = createUnencodedMessage -- see [note Serializable UnencodedMessage]
539+
540+-- | This is the /unsafe/ variant of 'wrapMessage'. See
541+-- "Control.Distributed.Process.UnsafePrimitives" for details.
542+unsafeWrapMessage :: Serializable a => a -> Message
543+unsafeWrapMessage = Unsafe.wrapMessage
544+
545+-- [note Serializable UnencodedMessage]
546+-- if we attempt to serialise an UnencodedMessage, it will be converted to an
547+-- EncodedMessage first, so we're ok here. See 'messageToPayload' in
548+-- Primitives.hs for the gory details
397549
398550-- | Attempt to unwrap a raw 'Message'.
399551-- If the type of the decoded message payload matches the expected type, the
@@ -406,14 +558,14 @@ wrapMessage = createMessage
406558-- Whereas this expression, will yield @Just "foo"@
407559-- > unwrapMessage (wrapMessage "foo") :: Process (Maybe String)
408560--
409−unwrapMessage :: forall a. Serializable a => Message -> Process (Maybe a)
561+unwrapMessage :: forall m a. (Monad m, Serializable a) => Message -> m (Maybe a)
410562unwrapMessage msg =
411563 case messageFingerprint msg == fingerprint (undefined :: a) of
412− False -> return Nothing
564+ False -> return Nothing :: m (Maybe a)
413565 True -> case msg of
414− (UnencodedMessage _ m) ->
415− let m' = unsafeCoerce m :: a
416− in return (Just m')
566+ (UnencodedMessage _ ms) ->
567+ let ms' = unsafeCoerce ms :: a
568+ in return (Just ms')
417569 (EncodedMessage _ _) ->
418570 return (Just (decoded))
419571 where
@@ -429,21 +581,53 @@ unwrapMessage msg =
429581--
430582-- Intended for use in `catchesExit` and `matchAny` primitives.
431583--
432−handleMessage :: forall a b. (Serializable a)
433− => Message -> (a -> Process b) -> Process (Maybe b)
434−handleMessage msg proc = do
584+handleMessage :: forall m a b. (Monad m, Serializable a)
585+ => Message -> (a -> m b) -> m (Maybe b)
586+handleMessage msg proc = handleMessageIf msg (const True) proc
587+
588+-- | Conditionally handle a raw 'Message'.
589+-- If the predicate @(a -> Bool)@ evaluates to @True@, invokes the supplied
590+-- handler, other returns @Nothing@ to indicate failure. See 'handleMessage'
591+-- for further information about runtime type checking.
592+--
593+handleMessageIf :: forall m a b . (Monad m, Serializable a)
594+ => Message
595+ -> (a -> Bool)
596+ -> (a -> m b)
597+ -> m (Maybe b)
598+handleMessageIf msg c proc = do
435599 case messageFingerprint msg == fingerprint (undefined :: a) of
436− False -> return Nothing
600+ False -> return Nothing :: m (Maybe b)
437601 True -> case msg of
438− (UnencodedMessage _ m) ->
439− let m' = unsafeCoerce m :: a -- note [decoding]
440− in do { r <- proc m'; return (Just r) }
602+ (UnencodedMessage _ ms) ->
603+ let ms' = unsafeCoerce ms :: a in
604+ case (c ms') of
605+ True -> do { r <- proc ms'; return (Just r) }
606+ False -> return Nothing :: m (Maybe b)
441607 (EncodedMessage _ _) ->
442− do { r <- proc (decoded :: a); return (Just r) }
608+ case (c decoded) of
609+ True -> do { r <- proc (decoded :: a); return (Just r) }
610+ False -> return Nothing :: m (Maybe b)
443611 where
444612 decoded :: a -- note [decoding]
445613 !decoded = decode (messageEncoding msg)
446614
615+-- | As 'handleMessage' but ignores result, which is useful if you don't
616+-- care whether or not the handler succeeded.
617+--
618+handleMessage_ :: forall m a . (Monad m, Serializable a)
619+ => Message -> (a -> m ()) -> m ()
620+handleMessage_ msg proc = handleMessageIf_ msg (const True) proc
621+
622+-- | Conditional version of 'handleMessage_'.
623+--
624+handleMessageIf_ :: forall m a . (Monad m, Serializable a)
625+ => Message
626+ -> (a -> Bool)
627+ -> (a -> m ())
628+ -> m ()
629+handleMessageIf_ msg c proc = handleMessageIf msg c proc >> return ()
630+
447631-- | Match against an arbitrary message. 'matchAny' removes the first available
448632-- message from the process mailbox. To handle arbitrary /raw/ messages once
449633-- removed from the mailbox, see 'handleMessage' and 'unwrapMessage'.
@@ -489,6 +673,36 @@ succeeds) that unsafeCoerce will not fail.
489673matchUnknown :: Process b -> Match b
490674matchUnknown p = Match $ MatchMsg (const (Just p))
491675
676+-- | Receives messages and forwards them to @pid@ if @p msg == True@.
677+delegate :: ProcessId -> (Message -> Bool) -> Process ()
678+delegate pid p = do
679+ receiveWait [
680+ matchAny (\m -> case (p m) of
681+ True -> forward m pid
682+ False -> return ())
683+ ]
684+ delegate pid p
685+
686+-- | A straight relay that forwards all messages to the supplied pid.
687+relay :: ProcessId -> Process ()
688+relay !pid = receiveWait [ matchAny (\m -> forward m pid) ] >> relay pid
689+
690+-- | Proxies @pid@ and forwards messages whenever @proc@ evaluates to @True@.
691+-- Unlike 'delegate' the predicate @proc@ runs in the 'Process' monad, allowing
692+-- for richer proxy behaviour. If @proc@ returns @False@ or the /runtime type check/
693+-- fails, no action is taken and the proxy process will continue running.
694+proxy :: Serializable a => ProcessId -> (a -> Process Bool) -> Process ()
695+proxy pid proc = do
696+ receiveWait [
697+ matchAny (\m -> do
698+ next <- handleMessage m proc
699+ case next of
700+ Just True -> forward m pid
701+ Just False -> return () -- explicitly ignored
702+ Nothing -> return ()) -- un-routable
703+ ]
704+ proxy pid proc
705+
492706--------------------------------------------------------------------------------
493707-- Process management --
494708--------------------------------------------------------------------------------
@@ -501,14 +715,17 @@ instance Exception ProcessTerminationException
501715
502716-- | Terminate immediately (throws a ProcessTerminationException)
503717terminate :: Process a
504−terminate = liftIO $ throwIO ProcessTerminationException
718+terminate = throwM ProcessTerminationException
505719
506720-- [Issue #110]
507721-- | Die immediately - throws a 'ProcessExitException' with the given @reason@.
508722die :: Serializable a => a -> Process b
509723die reason = do
724+-- NOTE: We do /not/ want to use UnencodedMessage here, as the exception
725+-- could be decoded by a handler passed to 'catchExit', re-thrown or even
726+-- passed to another process via the tracing mechanism.
510727 pid <- getSelfPid
511− liftIO $ throwIO (ProcessExitException pid (createMessage reason))
728+ throwM (ProcessExitException pid (createMessage reason))
512729
513730-- | Forceful request to kill a process. Where 'exit' provides an exception
514731-- that can be caught and handled, 'kill' throws an unexposed exception type
@@ -541,12 +758,12 @@ catchExit :: forall a b . (Show a, Serializable a)
541758 => Process b
542759 -> (ProcessId -> a -> Process b)
543760 -> Process b
544−catchExit act exitHandler = catch act handleExit
761+catchExit act exitHandler = Catch.catch act handleExit
545762 where
546763 handleExit ex@(ProcessExitException from msg) =
547764 if messageFingerprint msg == fingerprint (undefined :: a)
548765 then exitHandler from decoded
549− else liftIO $ throwIO ex
766+ else throwM ex
550767 where
551768 decoded :: a
552769 -- Make sure the value is fully decoded so that we don't hang to
@@ -556,21 +773,21 @@ catchExit act exitHandler = catch act handleExit
556773-- | Lift 'Control.Exception.catches' (almost).
557774--
558775-- As 'ProcessExitException' stores the exit @reason@ as a typed, encoded
559−-- message, a handler must accept an input of the expected type. In order to
776+-- message, a handler must accept inputs of the expected type. In order to
560777-- handle a list of potentially different handlers (and therefore input types),
561−-- a handler passed to 'catchesExit' must accept 'AbstractMessage' and return
778+-- a handler passed to 'catchesExit' must accept 'Message' and return
562779-- @Maybe@ (i.e., @Just p@ if it handled the exit reason, otherwise @Nothing@).
563780--
564−-- See 'maybeHandleMessage' and 'AsbtractMessage' for more details.
781+-- See 'maybeHandleMessage' and 'Message' for more details.
565782catchesExit :: Process b
566783 -> [(ProcessId -> Message -> (Process (Maybe b)))]
567784 -> Process b
568−catchesExit act handlers = catch act ((flip handleExit) handlers)
785+catchesExit act handlers = Catch.catch act ((flip handleExit) handlers)
569786 where
570787 handleExit :: ProcessExitException
571788 -> [(ProcessId -> Message -> Process (Maybe b))]
572789 -> Process b
573− handleExit ex [] = liftIO $ throwIO ex
790+ handleExit ex [] = throwM ex
574791 handleExit ex@(ProcessExitException from msg) (h:hs) = do
575792 r <- h from msg
576793 case r of
@@ -585,6 +802,31 @@ getSelfPid = processId <$> ask
585802getSelfNode :: Process NodeId
586803getSelfNode = localNodeId . processNode <$> ask
587804
805+
806+-- | Get statistics about the specified node
807+getNodeStats :: NodeId -> Process (Either DiedReason NodeStats)
808+getNodeStats nid = do
809+ selfNode <- getSelfNode
810+ if nid == selfNode
811+ then Right `fmap` getLocalNodeStats -- optimisation
812+ else getNodeStatsRemote selfNode
813+ where
814+ getNodeStatsRemote :: NodeId -> Process (Either DiedReason NodeStats)
815+ getNodeStatsRemote selfNode = do
816+ sendCtrlMsg (Just nid) $ GetNodeStats selfNode
817+ bracket (monitorNode nid) unmonitor $ \mRef ->
818+ receiveWait [ match (\(stats :: NodeStats) -> return $ Right stats)
819+ , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef)
820+ (\(NodeMonitorNotification _ _ dr) -> return $ Left dr)
821+ ]
822+
823+-- | Get statistics about our local node
824+getLocalNodeStats :: Process NodeStats
825+getLocalNodeStats = do
826+ self <- getSelfNode
827+ sendCtrlMsg Nothing $ GetNodeStats self
828+ receiveWait [ match (\(stats :: NodeStats) -> return stats) ]
829+
588830-- | Get information about the specified process
589831getProcessInfo :: ProcessId -> Process (Maybe ProcessInfo)
590832getProcessInfo pid =
@@ -654,12 +896,19 @@ monitor = monitor' . ProcessIdentifier
654896-- @withMonitor@ returns, there might still be unreceived monitor
655897-- messages in the queue.
656898--
657−withMonitor :: ProcessId -> Process a -> Process a
658−withMonitor pid code = bracket (monitor pid) unmonitor (\_ -> code)
659− -- unmonitor blocks waiting for the response, so there's a possibility
660− -- that an exception might interrupt withMonitor before the unmonitor
661− -- has completed. I think that's better than making the unmonitor
662− -- uninterruptible.
899+withMonitor :: ProcessId -> (MonitorRef -> Process a) -> Process a
900+withMonitor pid = bracket (monitor pid) unmonitor
901+
902+-- | Establishes temporary monitoring of another process.
903+--
904+-- @withMonitor_ pid code@ sets up monitoring of @pid@ for the duration
905+-- of @code@. Note: although monitoring is no longer active when
906+-- @withMonitor_@ returns, there might still be unreceived monitor
907+-- messages in the queue.
908+--
909+-- Since 0.6.1
910+withMonitor_ :: ProcessId -> Process a -> Process a
911+withMonitor_ p = withMonitor p . const
663912
664913-- | Remove a link
665914--
@@ -697,12 +946,21 @@ unlinkPort sport = do
697946-- | Remove a monitor
698947--
699948-- This has the same synchronous/asynchronous nature as 'unlink'.
949+--
950+-- ProcessMonitorNotification messages for the given MonitorRef are removed from
951+-- the mailbox.
700952unmonitor :: MonitorRef -> Process ()
701953unmonitor ref = do
702954 unmonitorAsync ref
703955 receiveWait [ matchIf (\(DidUnmonitor ref') -> ref' == ref)
704956 (\_ -> return ())
705957 ]
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+ ]
706964
707965--------------------------------------------------------------------------------
708966-- Exception handling --
@@ -710,60 +968,56 @@ unmonitor ref = do
710968
711969-- | Lift 'Control.Exception.catch'
712970catch :: Exception e => Process a -> (e -> Process a) -> Process a
713−catch p h = do
714− lproc <- ask
715− liftIO $ Ex.catch (runLocalProcess lproc p) (runLocalProcess lproc . h)
971+catch = Catch.catch
972+{-# DEPRECATED catch "Use Control.Monad.Catch.catch instead" #-}
716973
717974-- | Lift 'Control.Exception.try'
718975try :: Exception e => Process a -> Process (Either e a)
719−try p = do
720− lproc <- ask
721− liftIO $ Ex.try (runLocalProcess lproc p)
976+try = Catch.try
977+{-# DEPRECATED try "Use Control.Monad.Catch.try instead" #-}
722978
723979-- | Lift 'Control.Exception.mask'
724980mask :: ((forall a. Process a -> Process a) -> Process b) -> Process b
725−mask p = do
726− lproc <- ask
727− liftIO $ Ex.mask $ \restore ->
728− runLocalProcess lproc (p (liftRestore lproc restore))
729− where
730− liftRestore :: LocalProcess -> (forall a. IO a -> IO a) -> (forall a. Process a -> Process a)
731− liftRestore lproc restoreIO = liftIO . restoreIO . runLocalProcess lproc
981+mask = Catch.mask
982+{-# DEPRECATED mask "Use Control.Monad.Catch.mask_ instead" #-}
983+
984+-- | Lift 'Control.Exception.mask_'
985+mask_ :: Process a -> Process a
986+mask_ = Catch.mask_
987+{-# DEPRECATED mask_ "Use Control.Monad.Catch.mask_ instead" #-}
732988
733989-- | Lift 'Control.Exception.onException'
734990onException :: Process a -> Process b -> Process a
735−onException p what = p `catch` \e -> do _ <- what
736− liftIO $ throwIO (e :: SomeException)
991+onException = Catch.onException
992+{-# DEPRECATED onException "Use Control.Monad.Catch.onException instead" #-}
737993
738994-- | Lift 'Control.Exception.bracket'
739995bracket :: Process a -> (a -> Process b) -> (a -> Process c) -> Process c
740−bracket before after thing =
741− mask $ \restore -> do
742− a <- before
743− r <- restore (thing a) `onException` after a
744− _ <- after a
745− return r
996+bracket = Catch.bracket
997+{-# DEPRECATED bracket "Use Control.Monad.Catch.bracket instead" #-}
746998
747999-- | Lift 'Control.Exception.bracket_'
7481000bracket_ :: Process a -> Process b -> Process c -> Process c
749−bracket_ before after thing = bracket before (const after) (const thing)
1001+bracket_ = Catch.bracket_
1002+{-# DEPRECATED bracket_ "Use Control.Monad.Catch.bracket_ instead" #-}
7501003
7511004-- | Lift 'Control.Exception.finally'
7521005finally :: Process a -> Process b -> Process a
753−finally a sequel = bracket_ (return ()) sequel a
1006+finally = Catch.finally
1007+{-# DEPRECATED finally "Use Control.Monad.Catch.finally instead" #-}
7541008
7551009-- | You need this when using 'catches'
7561010data Handler a = forall e . Exception e => Handler (e -> Process a)
7571011
7581012instance Functor Handler where
759− fmap f (Handler h) = Handler (fmap f . h)
1013+ fmap f (Handler h) = Handler (fmap f . h)
7601014
7611015-- | Lift 'Control.Exception.catches'
7621016catches :: Process a -> [Handler a] -> Process a
763−catches proc handlers = proc `catch` catchesHandler handlers
1017+catches proc handlers = proc `Catch.catch` catchesHandler handlers
7641018
7651019catchesHandler :: [Handler a] -> SomeException -> Process a
766−catchesHandler handlers e = foldr tryHandler (throw e) handlers
1020+catchesHandler handlers e = foldr tryHandler (throwM e) handlers
7671021 where tryHandler (Handler handler) res
7681022 = case fromException e of
7691023 Just e' -> handler e'
@@ -783,7 +1037,10 @@ expectTimeout n = receiveTimeout n [match return]
7831037spawnAsync :: NodeId -> Closure (Process ()) -> Process SpawnRef
7841038spawnAsync nid proc = do
7851039 spawnRef <- getSpawnRef
786− sendCtrlMsg (Just nid) $ Spawn proc spawnRef
1040+ node <- getSelfNode
1041+ if nid == node
1042+ then sendCtrlMsg Nothing $ Spawn proc spawnRef
1043+ else sendCtrlMsg (Just nid) $ Spawn proc spawnRef
7871044 return spawnRef
7881045
7891046-- | Monitor a node (asynchronous)
@@ -829,27 +1086,60 @@ unlinkPortAsync (SendPort cid) =
8291086-- Logging --
8301087--------------------------------------------------------------------------------
8311088
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+
8321124-- | Log a string
8331125--
834−-- @say message@ sends a message (time, pid of the current process, message)
835−-- to the process registered as 'logger'. By default, this process simply
836−-- sends the string to 'stderr'. Individual Cloud Haskell backends might
837−-- replace this with a different logger process, however.
1126+-- @say message@ sends a message of type 'SayMessage' with the current time and
1127+-- 'ProcessId' of the current process to the process registered as @logger@. By
1128+-- default, this process simply sends the string to @stderr@. Individual Cloud
1129+-- Haskell backends might replace this with a different logger process, however.
8381130say :: String -> Process ()
8391131say string = do
8401132 now <- liftIO getCurrentTime
8411133 us <- getSelfPid
842− nsend "logger" (formatTime defaultTimeLocale "%c" now, us, string)
1134+ nsend "logger" (SayMessage now us string)
8431135
8441136--------------------------------------------------------------------------------
8451137-- Registry --
8461138--------------------------------------------------------------------------------
8471139
848−-- | Register a process with the local registry (asynchronous).
849−-- This version will wait until a response is gotten from the
850−-- management process. The name must not already be registered.
851−-- The process need not be on this node.
852−-- A bad registration will result in a 'ProcessRegistrationException'
1140+-- | Register a process with the local registry (synchronous). The name must not
1141+-- already be registered. The process need not be on this node. A bad
1142+-- registration will result in a 'ProcessRegistrationException'
8531143--
8541144-- The process to be registered does not have to be local itself.
8551145register :: String -> ProcessId -> Process ()
@@ -864,9 +1154,10 @@ registerImpl :: Bool -> String -> ProcessId -> Process ()
8641154registerImpl force label pid = do
8651155 mynid <- getSelfNode
8661156 sendCtrlMsg Nothing (Register label mynid (Just pid) force)
867− receiveWait [ matchIf (\(RegisterReply label' _) -> label == label')
868− (\(RegisterReply _ ok) -> handleRegistrationReply label ok)
869− ]
1157+ receiveWait
1158+ [ matchIf (\(RegisterReply label' _ _) -> label == label')
1159+ (\(RegisterReply _ ok owner) -> handleRegistrationReply label ok owner)
1160+ ]
8701161
8711162-- | Register a process with a remote registry (asynchronous).
8721163--
@@ -875,8 +1166,10 @@ registerImpl force label pid = do
8751166--
8761167-- See comments in 'whereisRemoteAsync'
8771168registerRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
878−registerRemoteAsync nid label pid =
879− sendCtrlMsg (Just nid) (Register label nid (Just pid) False)
1169+registerRemoteAsync nid label pid = do
1170+ here <- getSelfNode
1171+ sendCtrlMsg (if nid == here then Nothing else Just nid)
1172+ (Register label nid (Just pid) False)
8801173
8811174reregisterRemoteAsync :: NodeId -> String -> ProcessId -> Process ()
8821175reregisterRemoteAsync nid label pid =
@@ -889,16 +1182,17 @@ unregister :: String -> Process ()
8891182unregister label = do
8901183 mynid <- getSelfNode
8911184 sendCtrlMsg Nothing (Register label mynid Nothing False)
892− receiveWait [ matchIf (\(RegisterReply label' _) -> label == label')
893− (\(RegisterReply _ ok) -> handleRegistrationReply label ok)
894− ]
1185+ receiveWait
1186+ [ matchIf (\(RegisterReply label' _ _) -> label == label')
1187+ (\(RegisterReply _ ok owner) -> handleRegistrationReply label ok owner)
1188+ ]
8951189
8961190-- | Deal with the result from an attempted registration or unregistration
8971191-- by throwing an exception if necessary
898−handleRegistrationReply :: String -> Bool -> Process ()
899−handleRegistrationReply label ok =
1192+handleRegistrationReply :: String -> Bool -> Maybe ProcessId -> Process ()
1193+handleRegistrationReply label ok owner =
9001194 when (not ok) $
901− liftIO $ throwIO $ ProcessRegistrationException label
1195+ throwM $ ProcessRegistrationException label owner
9021196
9031197-- | Remove a process from a remote registry (asynchronous).
9041198--
@@ -927,18 +1221,45 @@ whereis label = do
9271221-- use 'monitorNode' and take appropriate action when you receive a
9281222-- 'NodeMonitorNotification').
9291223whereisRemoteAsync :: NodeId -> String -> Process ()
930−whereisRemoteAsync nid label =
931− sendCtrlMsg (Just nid) (WhereIs label)
1224+whereisRemoteAsync nid label = do
1225+ here <- getSelfNode
1226+ sendCtrlMsg (if nid == here then Nothing else Just nid) (WhereIs label)
9321227
9331228-- | Named send to a process in the local registry (asynchronous)
9341229nsend :: Serializable a => String -> a -> Process ()
935−nsend label msg =
936− sendCtrlMsg Nothing (NamedSend label (createMessage msg))
1230+nsend label msg = do
1231+ proc <- ask
1232+ let msg' = createUnencodedMessage msg
1233+ liftIO $ traceEvent (localEventBus (processNode proc))
1234+ (MxSentToName label (processId proc) msg')
1235+ sendCtrlMsg Nothing (NamedSend label msg')
1236+
1237+-- | Named send to a process in the local registry (asynchronous).
1238+-- This function makes /no/ attempt to serialize and (in the case when the
1239+-- destination process resides on the same local node) therefore ensure that
1240+-- the payload is fully evaluated before it is delivered.
1241+unsafeNSend :: Serializable a => String -> a -> Process ()
1242+unsafeNSend = Unsafe.nsend
9371243
9381244-- | Named send to a process in a remote registry (asynchronous)
9391245nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
940−nsendRemote nid label msg =
941− sendCtrlMsg (Just nid) (NamedSend label (createMessage msg))
1246+nsendRemote nid label msg = do
1247+ proc <- ask
1248+ let us = processId proc
1249+ let node = processNode proc
1250+ if localNodeId node == nid
1251+ then nsend label msg
1252+ else let lbl = label ++ "@" ++ show nid in do
1253+ liftIO $ traceEvent (localEventBus node)
1254+ (MxSentToName lbl us (wrapMessage msg))
1255+ sendCtrlMsg (Just nid) (NamedSend label (createMessage msg))
1256+
1257+-- | Named send to a process in a remote registry (asynchronous)
1258+-- This function makes /no/ attempt to serialize and (in the case when the
1259+-- destination process resides on the same local node) therefore ensure that
1260+-- the payload is fully evaluated before it is delivered.
1261+unsafeNSendRemote :: Serializable a => NodeId -> String -> a -> Process ()
1262+unsafeNSendRemote = Unsafe.nsendRemote
9421263
9431264--------------------------------------------------------------------------------
9441265-- Closures --
@@ -997,106 +1318,20 @@ reconnectPort them = do
9971318 node <- processNode <$> ask
9981319 liftIO $ disconnect node (ProcessIdentifier us) (SendPortIdentifier (sendPortId them))
9991320
1000−--------------------------------------------------------------------------------
1001−-- Debugging/Tracing --
1002−--------------------------------------------------------------------------------
1003−
1004−-- | Send a message to the internal (system) trace facility. If tracing is
1005−-- enabled, this will create a custom trace event. Note that several Cloud Haskell
1006−-- sub-systems also generate trace events for informational/debugging purposes,
1007−-- thus traces generated this way will not be the only output seen.
1008−--
1009−-- Just as with the "Debug.Trace" module, this is a debugging/tracing facility
1010−-- for use in development, and should not be used in a production setting -
1011−-- which is why the default behaviour is to trace to the GHC eventlog. For a
1012−-- general purpose logging facility, you should consider 'say'.
1013−--
1014−-- Trace events can be written to the GHC event log, a text file, or to the
1015−-- standard system logger process (see 'say'). The default behaviour for writing
1016−-- to the eventlog requires specific intervention to work, without which traces
1017−-- are silently dropped/ignored and no output will be generated.
1018−-- The GHC eventlog documentation provides information about enabling, viewing
1019−-- and working with event traces: <http://hackage.haskell.org/trac/ghc/wiki/EventLog>.
1020−--
1021−-- When a new local node is started, the contents of the environment variable
1022−-- @DISTRIBUTED_PROCESS_TRACE_FILE@ are checked for a valid file path. If this
1023−-- exists and the file can be opened for writing, all trace output will be directed
1024−-- thence. If the environment variable is empty, the path invalid, or the file
1025−-- unavailable for writing - e.g., because another node has already started
1026−-- tracing to it - then the @DISTRIBUTED_PROCESS_TRACE_CONSOLE@ environment
1027−-- variable is checked for /any/ non-empty value. If this is set, then all trace
1028−-- output will be directed to the system logger process. If neither evironment
1029−-- variable provides a valid trace configuration, all internal traces are written
1030−-- to "Debug.Trace.traceEventIO", which writes to the GHC eventlog.
1031−--
1032−-- Users of the /simplelocalnet/ Cloud Haskell backend should also note that
1033−-- because the trace file option only supports trace output from a single node
1034−-- (so as to avoid interleaving), a file trace configured for the master node will
1035−-- prevent slaves from tracing to the file and they will fall back to using the
1036−-- console/'say' or eventlog instead.
1037−--
1038−trace :: String -> Process ()
1039−trace s = do
1040− node <- processNode <$> ask
1041− liftIO $ Trace.trace (localTracer node) s
1042−
1043−--------------------------------------------------------------------------------
1044−-- Messages to local processes --
1045−--------------------------------------------------------------------------------
1046−
1047−deliverToLocalProcess :: ProcessId -> Message -> Process ()
1048−deliverToLocalProcess pid msg = do
1049− withLocalProc pid $ \p -> enqueue (processQueue p) msg
1050−
1051−deliverToLocalPort :: SendPortId -> Message -> Process ()
1052−deliverToLocalPort spId msg =
1053− let pid = sendPortProcessId spId
1054− cid = sendPortLocalId spId
1055− in do
1056− withLocalProc pid $ \proc -> do
1057− mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
1058− case mChan of
1059− -- in the unlikely event we know nothing about this channel id,
1060− -- see [note: missing recipients]
1061− Nothing -> return ()
1062− Just (TypedChannel chan') -> do
1063− -- If ch is Nothing, the process has given up the read end of
1064− -- the channel and we simply ignore the incoming message - this
1065− ch <- deRefWeak chan'
1066− forM_ ch $ \chan -> deliverChan msg chan
1067− where deliverChan :: forall a . Message -> TQueue a -> IO ()
1068− deliverChan (UnencodedMessage _ raw) chan' =
1069− atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
1070− deliverChan (EncodedMessage _ _) _ =
1071− -- this will /never/ happen
1072− error "invalid local channel delivery"
1073−
1074−withLocalProc :: ProcessId -> (LocalProcess -> IO ()) -> Process ()
1075−withLocalProc pid p = do
1076− node <- processNode <$> ask
1077− liftIO $ do
1078− -- see [note: missing recipients]
1079− let lpid = processLocalId pid
1080− mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1081− forM_ mProc p
1082−
1083−-- [note: missing recipients]
1084−-- By [Unified: table 6, rule missing_process] messages to dead processes
1085−-- can silently be dropped
1086−
10871321--------------------------------------------------------------------------------
10881322-- Auxiliary functions --
10891323--------------------------------------------------------------------------------
10901324
10911325sendLocal :: (Serializable a) => ProcessId -> a -> Process ()
1092−sendLocal pid msg =
1093− -- sendCtrlMsg Nothing $ LocalSend pid (createUnencodedMessage msg)
1094− deliverToLocalProcess pid (createUnencodedMessage msg)
1326+sendLocal to msg =
1327+ sendCtrlMsg Nothing $ LocalSend to (createUnencodedMessage msg)
10951328
10961329sendChanLocal :: (Serializable a) => SendPortId -> a -> Process ()
10971330sendChanLocal spId msg =
1098− -- sendCtrlMsg Nothing $ LocalPortSend spId (createUnencodedMessage msg)
1099− deliverToLocalPort spId (createUnencodedMessage msg)
1331+ -- we *must* fully serialize/encode the message here, because
1332+ -- attempting to use `unsafeCoerce' in the node controller
1333+ -- won't work since we know nothing about the required type
1334+ sendCtrlMsg Nothing $ LocalPortSend spId (createUnencodedMessage msg)
11001335
11011336getMonitorRefFor :: Identifier -> Process MonitorRef
11021337getMonitorRefFor ident = do
@@ -1126,22 +1361,3 @@ monitor' ident = do
11261361-- | Link to a process/node/channel
11271362link' :: Identifier -> Process ()
11281363link' = sendCtrlMsg Nothing . Link
1129−
1130−-- Send a control message
1131−sendCtrlMsg :: Maybe NodeId -- ^ Nothing for the local node
1132− -> ProcessSignal -- ^ Message to send
1133− -> Process ()
1134−sendCtrlMsg mNid signal = do
1135− proc <- ask
1136− let msg = NCMsg { ctrlMsgSender = ProcessIdentifier (processId proc)
1137− , ctrlMsgSignal = signal
1138− }
1139− case mNid of
1140− Nothing -> do
1141− liftIO $ writeChan (localCtrlChan (processNode proc)) msg
1142− Just nid ->
1143− liftIO $ sendBinary (processNode proc)
1144− (ProcessIdentifier (processId proc))
1145− (NodeIdentifier nid)
1146− WithImplicitReconnect
1147− msg
src/Control/Distributed/Process/Internal/Spawn.hsnew file+165
@@ -0,0 +1,165 @@
1+{-# LANGUAGE RankNTypes #-}
2+module Control.Distributed.Process.Internal.Spawn
3+ ( spawn
4+ , spawnLink
5+ , spawnMonitor
6+ , call
7+ , spawnSupervised
8+ , spawnChannel
9+ ) where
10+
11+import Control.Distributed.Static
12+ ( Static
13+ , Closure
14+ , closureCompose
15+ , staticClosure
16+ )
17+import Control.Distributed.Process.Internal.Types
18+ ( NodeId(..)
19+ , ProcessId(..)
20+ , Process(..)
21+ , MonitorRef(..)
22+ , ProcessMonitorNotification(..)
23+ , NodeMonitorNotification(..)
24+ , DidSpawn(..)
25+ , SendPort(..)
26+ , ReceivePort(..)
27+ , nullProcessId
28+ )
29+import Control.Distributed.Process.Serializable (Serializable, SerializableDict)
30+import Control.Distributed.Process.Internal.Closure.BuiltIn
31+ ( sdictSendPort
32+ , sndStatic
33+ , idCP
34+ , seqCP
35+ , bindCP
36+ , splitCP
37+ , cpLink
38+ , cpSend
39+ , cpNewChan
40+ , cpDelayed
41+ , returnCP
42+ , sdictUnit
43+ )
44+import Control.Distributed.Process.Internal.Primitives
45+ ( -- Basic messaging
46+ usend
47+ , expect
48+ , receiveWait
49+ , match
50+ , matchIf
51+ , link
52+ , getSelfPid
53+ , monitor
54+ , monitorNode
55+ , unmonitor
56+ , spawnAsync
57+ , reconnect
58+ )
59+
60+-- | Spawn a process
61+--
62+-- For more information about 'Closure', see
63+-- "Control.Distributed.Process.Closure".
64+--
65+-- See also 'call'.
66+spawn :: NodeId -> Closure (Process ()) -> Process ProcessId
67+spawn nid proc = do
68+ us <- getSelfPid
69+ mRef <- monitorNode nid
70+ sRef <- spawnAsync nid (cpDelayed us proc)
71+ receiveWait [
72+ matchIf (\(DidSpawn ref _) -> ref == sRef) $ \(DidSpawn _ pid) -> do
73+ unmonitor mRef
74+ usend pid ()
75+ return pid
76+ , matchIf (\(NodeMonitorNotification ref _ _) -> ref == mRef) $ \_ ->
77+ return (nullProcessId nid)
78+ ]
79+
80+-- | Spawn a process and link to it
81+--
82+-- Note that this is just the sequential composition of 'spawn' and 'link'.
83+-- (The "Unified" semantics that underlies Cloud Haskell does not even support
84+-- a synchronous link operation)
85+spawnLink :: NodeId -> Closure (Process ()) -> Process ProcessId
86+spawnLink nid proc = do
87+ pid <- spawn nid proc
88+ link pid
89+ return pid
90+
91+-- | Like 'spawnLink', but monitor the spawned process
92+spawnMonitor :: NodeId -> Closure (Process ()) -> Process (ProcessId, MonitorRef)
93+spawnMonitor nid proc = do
94+ pid <- spawn nid proc
95+ ref <- monitor pid
96+ return (pid, ref)
97+
98+-- | Run a process remotely and wait for it to reply
99+--
100+-- We monitor the remote process: if it dies before it can send a reply, we die
101+-- too.
102+--
103+-- For more information about 'Static', 'SerializableDict', and 'Closure', see
104+-- "Control.Distributed.Process.Closure".
105+--
106+-- See also 'spawn'.
107+call :: Serializable a
108+ => Static (SerializableDict a)
109+ -> NodeId
110+ -> Closure (Process a)
111+ -> Process a
112+call dict nid proc = do
113+ us <- getSelfPid
114+ (pid, mRef) <- spawnMonitor nid (proc `bindCP`
115+ cpSend dict us `seqCP`
116+ -- Delay so the process does not terminate
117+ -- before the response arrives.
118+ cpDelayed us (returnCP sdictUnit ())
119+ )
120+ mResult <- receiveWait
121+ [ match $ \a -> usend pid () >> return (Right a)
122+ , matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)
123+ (\(ProcessMonitorNotification _ _ reason) -> return (Left reason))
124+ ]
125+ case mResult of
126+ Right a -> do
127+ -- Wait for the monitor message so that we the mailbox doesn't grow
128+ receiveWait
129+ [ matchIf (\(ProcessMonitorNotification ref _ _) -> ref == mRef)
130+ (\(ProcessMonitorNotification {}) -> return ())
131+ ]
132+ -- Clean up connection to pid
133+ reconnect pid
134+ return a
135+ Left err ->
136+ fail $ "call: remote process died: " ++ show err
137+
138+-- | Spawn a child process, have the child link to the parent and the parent
139+-- monitor the child
140+spawnSupervised :: NodeId
141+ -> Closure (Process ())
142+ -> Process (ProcessId, MonitorRef)
143+spawnSupervised nid proc = do
144+ us <- getSelfPid
145+ them <- spawn nid (cpLink us `seqCP` proc)
146+ ref <- monitor them
147+ return (them, ref)
148+
149+-- | Spawn a new process, supplying it with a new 'ReceivePort' and return
150+-- the corresponding 'SendPort'.
151+spawnChannel :: forall a. Serializable a => Static (SerializableDict a)
152+ -> NodeId
153+ -> Closure (ReceivePort a -> Process ())
154+ -> Process (SendPort a)
155+spawnChannel dict nid proc = do
156+ us <- getSelfPid
157+ _ <- spawn nid (go us)
158+ expect
159+ where
160+ go :: ProcessId -> Closure (Process ())
161+ go pid = cpNewChan dict
162+ `bindCP`
163+ (cpSend (sdictSendPort dict) pid `splitCP` proc)
164+ `bindCP`
165+ (idCP `closureCompose` staticClosure sndStatic)
src/Control/Distributed/Process/Internal/StrictMVar.hs+37−5
@@ -1,33 +1,43 @@
11-- | Like Control.Concurrent.MVar.Strict but reduce to HNF, not NF
2−{-# LANGUAGE MagicHash, UnboxedTuples #-}
2+{-# LANGUAGE CPP, MagicHash, UnboxedTuples #-}
33module Control.Distributed.Process.Internal.StrictMVar
44 ( StrictMVar(StrictMVar)
55 , newEmptyMVar
66 , newMVar
77 , takeMVar
88 , putMVar
9+ , readMVar
910 , withMVar
1011 , modifyMVar_
1112 , modifyMVar
13+ , modifyMVarMasked
1214 , mkWeakMVar
1315 ) where
1416
1517import Control.Applicative ((<$>))
1618import Control.Monad ((>=>))
19+#if MIN_VERSION_base(4,6,0)
1720import Control.Exception (evaluate)
21+#else
22+import Control.Exception (evaluate, mask_, onException)
23+#endif
1824import qualified Control.Concurrent.MVar as MVar
1925 ( MVar
2026 , newEmptyMVar
2127 , newMVar
2228 , takeMVar
2329 , putMVar
30+ , readMVar
2431 , withMVar
2532 , modifyMVar_
2633 , modifyMVar
34+#if MIN_VERSION_base(4,6,0)
35+ , modifyMVarMasked
36+#endif
2737 )
2838import GHC.MVar (MVar(MVar))
29−import GHC.IO (IO(IO))
30−import GHC.Prim (mkWeak#)
39+import GHC.IO (IO(IO), unIO)
40+import GHC.Exts (mkWeak#)
3141import GHC.Weak (Weak(Weak))
3242
3343newtype StrictMVar a = StrictMVar (MVar.MVar a)
@@ -44,6 +54,9 @@ takeMVar (StrictMVar v) = MVar.takeMVar v
4454putMVar :: StrictMVar a -> a -> IO ()
4555putMVar (StrictMVar v) x = evaluate x >> MVar.putMVar v x
4656
57+readMVar :: StrictMVar a -> IO a
58+readMVar (StrictMVar v) = MVar.readMVar v
59+
4760withMVar :: StrictMVar a -> (a -> IO b) -> IO b
4861withMVar (StrictMVar v) = MVar.withMVar v
4962
@@ -56,6 +69,25 @@ modifyMVar (StrictMVar v) f = MVar.modifyMVar v (f >=> evaluateFst)
5669 evaluateFst :: (a, b) -> IO (a, b)
5770 evaluateFst (x, y) = evaluate x >> return (x, y)
5871
72+modifyMVarMasked :: StrictMVar a -> (a -> IO (a, b)) -> IO b
73+modifyMVarMasked (StrictMVar v) f =
74+#if MIN_VERSION_base(4,6,0)
75+ MVar.modifyMVarMasked v (f >=> evaluateFst)
76+#else
77+ mask_ $ do
78+ a <- MVar.takeMVar v
79+ (a',b) <- (f a >>= evaluate) `onException` MVar.putMVar v a
80+ MVar.putMVar v a'
81+ return b
82+#endif
83+ where
84+ evaluateFst :: (a, b) -> IO (a, b)
85+ evaluateFst (x, y) = evaluate x >> return (x, y)
86+
5987mkWeakMVar :: StrictMVar a -> IO () -> IO (Weak (StrictMVar a))
60−mkWeakMVar m@(StrictMVar (MVar m#)) f = IO $ \s ->
61− case mkWeak# m# m f s of (# s1, w #) -> (# s1, Weak w #)
88+mkWeakMVar q@(StrictMVar (MVar m#)) f = IO $ \s ->
89+#if MIN_VERSION_base(4,9,0)
90+ case mkWeak# m# q (unIO f) s of (# s', w #) -> (# s', Weak w #)
91+#else
92+ case mkWeak# m# q f s of (# s', w #) -> (# s', Weak w #)
93+#endif
src/Control/Distributed/Process/Internal/Trace.hsdeleted−122
@@ -1,122 +0,0 @@
1−-- | Simple (internal) system logging/tracing support.
2−module Control.Distributed.Process.Internal.Trace
3− ( Tracer
4− , TraceArg(..)
5− , trace
6− , traceFormat
7− , startTracing
8− , stopTracer
9− ) where
10−
11−import Control.Concurrent (forkIO)
12−import Control.Concurrent.Chan (writeChan)
13−import Control.Concurrent.STM
14− ( TQueue
15− , newTQueueIO
16− , readTQueue
17− , writeTQueue
18− , atomically
19− )
20−import Control.Distributed.Process.Internal.Types
21− ( Tracer(..)
22− , LocalNode(..)
23− , NCMsg(..)
24− , Identifier(ProcessIdentifier)
25− , ProcessSignal(NamedSend)
26− , forever'
27− , nullProcessId
28− , createMessage
29− )
30−import Control.Exception
31− ( catch
32− , throwTo
33− , SomeException
34− , AsyncException(ThreadKilled)
35− )
36−import Data.List (intersperse)
37−import Data.Time.Clock (getCurrentTime)
38−import Data.Time.Format (formatTime)
39−import Debug.Trace (traceEventIO)
40−
41−import Prelude hiding (catch)
42−
43−import System.Environment (getEnv)
44−import System.IO
45− ( Handle
46− , IOMode(AppendMode)
47− , BufferMode(..)
48− , openFile
49− , hClose
50− , hPutStrLn
51− , hSetBuffering
52− )
53−import System.Locale (defaultTimeLocale)
54−
55−data TraceArg =
56− TraceStr String
57− | forall a. (Show a) => Trace a
58−
59−startTracing :: LocalNode -> IO LocalNode
60−startTracing node = do
61− tracer <- defaultTracer node
62− return node { localTracer = tracer }
63−
64−defaultTracer :: LocalNode -> IO Tracer
65−defaultTracer node = do
66− catch (getEnv "DISTRIBUTED_PROCESS_TRACE_FILE" >>= logfileTracer)
67− (\(_ :: IOError) -> defaultTracerAux node)
68−
69−defaultTracerAux :: LocalNode -> IO Tracer
70−defaultTracerAux node = do
71− catch (getEnv "DISTRIBUTED_PROCESS_TRACE_CONSOLE" >> procTracer node)
72− (\(_ :: IOError) -> return (EventLogTracer traceEventIO))
73− where procTracer :: LocalNode -> IO Tracer
74− procTracer n = return $ (LocalNodeTracer n)
75−
76−logfileTracer :: FilePath -> IO Tracer
77−logfileTracer p = do
78− q <- newTQueueIO
79− h <- openFile p AppendMode
80− hSetBuffering h LineBuffering
81− tid <- forkIO $ logger h q `catch` (\(_ :: SomeException) ->
82− hClose h >> return ())
83− return $ LogFileTracer tid q h
84− where logger :: Handle -> TQueue String -> IO ()
85− logger h q' = forever' $ do
86− msg <- atomically $ readTQueue q'
87− now <- getCurrentTime
88− hPutStrLn h $ msg ++ (formatTime defaultTimeLocale " - %c" now)
89−
90−-- TODO: compatibility layer for GHC/base versions (e.g., where's killThread?)
91−
92−stopTracer :: Tracer -> IO () -- overzealous but harmless duplication of hClose
93−stopTracer (LogFileTracer tid _ h) = throwTo tid ThreadKilled >> hClose h
94−stopTracer _ = return ()
95−
96−trace :: Tracer -> String -> IO ()
97−trace (LogFileTracer _ q _) msg = atomically $ writeTQueue q msg
98−trace (LocalNodeTracer n) msg = sendTraceMsg n msg
99−trace (EventLogTracer t) msg = t msg
100−trace InactiveTracer _ = return ()
101−
102−traceFormat :: Tracer
103− -> String
104− -> [TraceArg]
105− -> IO ()
106−traceFormat t d ls =
107− trace t $ concat (intersperse d (map toS ls))
108− where toS :: TraceArg -> String
109− toS (TraceStr s) = s
110− toS (Trace a) = show a
111−
112−sendTraceMsg :: LocalNode -> String -> IO ()
113−sendTraceMsg node string = do
114− now <- getCurrentTime
115− msg <- return $ (formatTime defaultTimeLocale "%c" now, string)
116− emptyPid <- return $ (nullProcessId (localNodeId node))
117− traceMsg <- return $ NCMsg {
118− ctrlMsgSender = ProcessIdentifier (emptyPid)
119− , ctrlMsgSignal = (NamedSend "logger" (createMessage msg))
120− }
121− writeChan (localCtrlChan node) traceMsg
122−
src/Control/Distributed/Process/Internal/Types.hs+253−51
@@ -1,3 +1,13 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE ExistentialQuantification #-}
3+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE GADTs #-}
5+{-# LANGUAGE DeriveGeneric #-}
6+{-# LANGUAGE CPP #-}
7+{-# LANGUAGE MultiParamTypeClasses #-}
8+{-# LANGUAGE TypeFamilies #-}
9+{-# LANGUAGE Rank2Types #-}
10+
111-- | Types used throughout the Cloud Haskell framework
212--
313-- We collect all types used internally in a single module because
@@ -14,8 +24,14 @@ module Control.Distributed.Process.Internal.Types
1424 , nullProcessId
1525 -- * Local nodes and processes
1626 , LocalNode(..)
17− , Tracer(..)
1827 , LocalNodeState(..)
28+ , ValidLocalNodeState(..)
29+ , NodeClosedException(..)
30+ , withValidLocalState
31+ , modifyValidLocalState
32+ , modifyValidLocalState_
33+ , Tracer(..)
34+ , MxEventBus(..)
1935 , LocalProcess(..)
2036 , LocalProcessState(..)
2137 , Process(..)
@@ -29,8 +45,10 @@ module Control.Distributed.Process.Internal.Types
2945 , ReceivePort(..)
3046 -- * Messages
3147 , Message(..)
48+ , isEncoded
3249 , createMessage
3350 , createUnencodedMessage
51+ , unsafeCreateUnencodedMessage
3452 , messageToPayload
3553 , payloadToMessage
3654 -- * Node controller user-visible data types
@@ -39,7 +57,6 @@ module Control.Distributed.Process.Internal.Types
3957 , NodeMonitorNotification(..)
4058 , PortMonitorNotification(..)
4159 , ProcessExitException(..)
42− , exitSource
4360 , ProcessLinkException(..)
4461 , NodeLinkException(..)
4562 , PortLinkException(..)
@@ -55,6 +72,7 @@ module Control.Distributed.Process.Internal.Types
5572 , RegisterReply(..)
5673 , ProcessInfo(..)
5774 , ProcessInfoNone(..)
75+ , NodeStats(..)
5876 -- * Node controller internal data types
5977 , NCMsg(..)
6078 , ProcessSignal(..)
@@ -77,7 +95,8 @@ module Control.Distributed.Process.Internal.Types
7795import System.Mem.Weak (Weak)
7896import Data.Map (Map)
7997import Data.Int (Int32)
80−import Data.Typeable (Typeable)
98+import Data.Data (Data)
99+import Data.Typeable (Typeable, typeOf)
81100import Data.Binary (Binary(put, get), putWord8, getWord8, encode)
82101import qualified Data.ByteString as BSS (ByteString, concat, copy)
83102import qualified Data.ByteString.Lazy as BSL
@@ -90,15 +109,18 @@ import qualified Data.ByteString.Lazy as BSL
90109import qualified Data.ByteString.Lazy.Internal as BSL (ByteString(..))
91110import Data.Accessor (Accessor, accessor)
92111import Control.Category ((>>>))
93−import Control.Exception (Exception)
112+import Control.DeepSeq (NFData(..))
113+import Control.Exception (Exception, throwIO)
94114import Control.Concurrent (ThreadId)
95115import Control.Concurrent.Chan (Chan)
96116import Control.Concurrent.STM (STM)
97−import qualified Control.Concurrent.STM as STM (TQueue)
117+import Control.Concurrent.STM.TChan (TChan)
118+import Control.Monad.Catch (MonadThrow(..), MonadCatch(..), MonadMask(..))
98119import qualified Network.Transport as NT (EndPoint, EndPointAddress, Connection)
99−import Control.Applicative (Applicative, Alternative, (<$>), (<*>))
120+import Control.Applicative
121+import Control.Monad.Fix (MonadFix)
100122import Control.Monad.Reader (MonadReader(..), ReaderT, runReaderT)
101−import Control.Monad.IO.Class (MonadIO)
123+import Control.Monad.IO.Class (MonadIO(..))
102124import Control.Distributed.Process.Serializable
103125 ( Fingerprint
104126 , Serializable
@@ -109,11 +131,19 @@ import Control.Distributed.Process.Serializable
109131 , showFingerprint
110132 )
111133import Control.Distributed.Process.Internal.CQueue (CQueue)
112−import Control.Distributed.Process.Internal.StrictMVar (StrictMVar)
134+import Control.Distributed.Process.Internal.StrictMVar
135+ ( StrictMVar
136+ , withMVar
137+ , modifyMVar
138+ , modifyMVar_
139+ )
113140import Control.Distributed.Process.Internal.WeakTQueue (TQueue)
114141import Control.Distributed.Static (RemoteTable, Closure)
115142import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC (mapMaybe)
116−import System.IO (Handle)
143+
144+import Data.Hashable
145+import GHC.Generics
146+import Prelude
117147
118148--------------------------------------------------------------------------------
119149-- Node and process identifiers --
@@ -121,8 +151,10 @@ import System.IO (Handle)
121151
122152-- | Node identifier
123153newtype NodeId = NodeId { nodeAddress :: NT.EndPointAddress }
124− deriving (Eq, Ord, Binary, Typeable)
125−
154+ deriving (Eq, Ord, Typeable, Data, Generic)
155+instance Binary NodeId where
156+instance NFData NodeId where rnf (NodeId a) = rnf a `seq` ()
157+instance Hashable NodeId where
126158instance Show NodeId where
127159 show (NodeId addr) = "nid://" ++ show addr
128160
@@ -132,7 +164,9 @@ data LocalProcessId = LocalProcessId
132164 { lpidUnique :: {-# UNPACK #-} !Int32
133165 , lpidCounter :: {-# UNPACK #-} !Int32
134166 }
135− deriving (Eq, Ord, Typeable, Show)
167+ deriving (Eq, Ord, Typeable, Data, Generic, Show)
168+
169+instance Hashable LocalProcessId where
136170
137171-- | Process identifier
138172data ProcessId = ProcessId
@@ -141,7 +175,11 @@ data ProcessId = ProcessId
141175 -- | Node-local identifier for the process
142176 , processLocalId :: {-# UNPACK #-} !LocalProcessId
143177 }
144− deriving (Eq, Ord, Typeable)
178+ deriving (Eq, Ord, Typeable, Data, Generic)
179+
180+instance Binary ProcessId where
181+instance NFData ProcessId where rnf (ProcessId n _) = rnf n `seq` ()
182+instance Hashable ProcessId where
145183
146184instance Show ProcessId where
147185 show (ProcessId (NodeId addr) (LocalProcessId _ lid))
@@ -152,7 +190,13 @@ data Identifier =
152190 NodeIdentifier !NodeId
153191 | ProcessIdentifier !ProcessId
154192 | SendPortIdentifier !SendPortId
155− deriving (Eq, Ord)
193+ deriving (Eq, Ord, Generic)
194+
195+instance Hashable Identifier where
196+instance NFData Identifier where
197+ rnf (NodeIdentifier n) = rnf n `seq` ()
198+ rnf (ProcessIdentifier n) = rnf n `seq` ()
199+ rnf n@SendPortIdentifier{} = n `seq` ()
156200
157201instance Show Identifier where
158202 show (NodeIdentifier nid) = show nid
@@ -183,12 +227,30 @@ nullProcessId nid =
183227-- Local nodes and processes --
184228--------------------------------------------------------------------------------
185229
186−-- | Required for system tracing in the node controller
187−data Tracer =
188− LogFileTracer !ThreadId !(STM.TQueue String) !Handle
189− | EventLogTracer !(String -> IO ())
190− | LocalNodeTracer !LocalNode
191− | InactiveTracer -- NB: never used, this is required to initialize LocalNode
230+-- | Provides access to the trace controller
231+data Tracer = Tracer
232+ {
233+ -- | Process id for the currently active trace handler
234+ tracerPid :: !ProcessId
235+ -- | Weak reference to the tracer controller's mailbox
236+ , weakQ :: !(Weak (CQueue Message))
237+ }
238+
239+-- | Local system management event bus state
240+data MxEventBus =
241+ MxEventBusInitialising
242+ | MxEventBus
243+ {
244+ -- | Process id of the management agent controller process
245+ agent :: !ProcessId
246+ -- | Configuration for the local trace controller
247+ , tracer :: !Tracer
248+ -- | Weak reference to the management agent controller's mailbox
249+ , evbuss :: !(Weak (CQueue Message))
250+ -- | API for adding management agents to a running node
251+ , mxNew :: !(((TChan Message, TChan Message) -> Process ()) -> IO ProcessId)
252+-- , mxReg :: !(StrictMVar (Map MxAgentId ))
253+ }
192254
193255-- | Local nodes
194256data LocalNode = LocalNode
@@ -200,8 +262,8 @@ data LocalNode = LocalNode
200262 , localState :: !(StrictMVar LocalNodeState)
201263 -- | Channel for the node controller
202264 , localCtrlChan :: !(Chan NCMsg)
203− -- | Current active system debug/trace log
204− , localTracer :: !Tracer
265+ -- | Internal management event bus
266+ , localEventBus :: !MxEventBus
205267 -- | Runtime lookup table for supporting closures
206268 -- TODO: this should be part of the CH state, not the local endpoint state
207269 , remoteTable :: !RemoteTable
@@ -211,7 +273,11 @@ data ImplicitReconnect = WithImplicitReconnect | NoImplicitReconnect
211273 deriving (Eq, Show)
212274
213275-- | Local node state
214−data LocalNodeState = LocalNodeState
276+data LocalNodeState =
277+ LocalNodeValid {-# UNPACK #-} !ValidLocalNodeState
278+ | LocalNodeClosed
279+
280+data ValidLocalNodeState = ValidLocalNodeState
215281 { -- | Processes running on this node
216282 _localProcesses :: !(Map LocalProcessId LocalProcess)
217283 -- | Counter to assign PIDs
@@ -224,6 +290,40 @@ data LocalNodeState = LocalNodeState
224290 (NT.Connection, ImplicitReconnect))
225291 }
226292
293+-- | Thrown by some primitives when they notice the node has been closed.
294+data NodeClosedException = NodeClosedException NodeId
295+ deriving (Show, Typeable)
296+
297+instance Exception NodeClosedException
298+
299+-- | Wrapper around 'withMVar' that checks that the local node is still in
300+-- a valid state.
301+withValidLocalState :: LocalNode
302+ -> (ValidLocalNodeState -> IO r)
303+ -> IO r
304+withValidLocalState node f = withMVar (localState node) $ \st -> case st of
305+ LocalNodeValid vst -> f vst
306+ LocalNodeClosed -> throwIO $ NodeClosedException (localNodeId node)
307+
308+-- | Wrapper around 'modifyMVar' that checks that the local node is still in
309+-- a valid state.
310+modifyValidLocalState :: LocalNode
311+ -> (ValidLocalNodeState -> IO (ValidLocalNodeState, a))
312+ -> IO (Maybe a)
313+modifyValidLocalState node f = modifyMVar (localState node) $ \st -> case st of
314+ LocalNodeValid vst -> do (vst', a) <- f vst
315+ return (LocalNodeValid vst', Just a)
316+ LocalNodeClosed -> return (LocalNodeClosed, Nothing)
317+
318+-- | Wrapper around 'modifyMVar_' that checks that the local node is still in
319+-- a valid state.
320+modifyValidLocalState_ :: LocalNode
321+ -> (ValidLocalNodeState -> IO ValidLocalNodeState)
322+ -> IO ()
323+modifyValidLocalState_ node f = modifyMVar_ (localState node) $ \st -> case st of
324+ LocalNodeValid vst -> LocalNodeValid <$> f vst
325+ LocalNodeClosed -> return LocalNodeClosed
326+
227327-- | Processes running on our local node
228328data LocalProcess = LocalProcess
229329 { processQueue :: !(CQueue Message)
@@ -250,7 +350,50 @@ data LocalProcessState = LocalProcessState
250350newtype Process a = Process {
251351 unProcess :: ReaderT LocalProcess IO a
252352 }
253− deriving (Functor, Monad, MonadIO, MonadReader LocalProcess, Typeable, Applicative)
353+ deriving ( Applicative
354+ , Functor
355+ , Monad
356+ , MonadFix
357+ , MonadIO
358+ , MonadReader LocalProcess
359+ , Typeable
360+ )
361+
362+instance MonadThrow Process where
363+ throwM = liftIO . throwIO
364+instance MonadCatch Process where
365+ catch p h = do
366+ lproc <- ask
367+ liftIO $ catch (runLocalProcess lproc p) (runLocalProcess lproc . h)
368+instance MonadMask Process where
369+#if MIN_VERSION_exceptions(0,10,0)
370+ generalBracket acquire release inner = do
371+ lproc <- ask
372+ liftIO $
373+ generalBracket (runLocalProcess lproc acquire)
374+ (\a e -> runLocalProcess lproc $ release a e)
375+ (runLocalProcess lproc . inner)
376+#endif
377+ mask p = do
378+ lproc <- ask
379+ liftIO $ mask $ \restore ->
380+ runLocalProcess lproc (p (liftRestore restore))
381+ where
382+ liftRestore :: (forall a. IO a -> IO a)
383+ -> (forall a. Process a -> Process a)
384+ liftRestore restoreIO = \p2 -> do
385+ ourLocalProc <- ask
386+ liftIO $ restoreIO $ runLocalProcess ourLocalProc p2
387+ uninterruptibleMask p = do
388+ lproc <- ask
389+ liftIO $ uninterruptibleMask $ \restore ->
390+ runLocalProcess lproc (p (liftRestore restore))
391+ where
392+ liftRestore :: (forall a. IO a -> IO a)
393+ -> (forall a. Process a -> Process a)
394+ liftRestore restoreIO = \p2 -> do
395+ ourLocalProc <- ask
396+ liftIO $ restoreIO $ runLocalProcess ourLocalProc p2
254397
255398--------------------------------------------------------------------------------
256399-- Typed channels --
@@ -268,12 +411,16 @@ data SendPortId = SendPortId {
268411 -- | Process-local ID of the channel
269412 , sendPortLocalId :: {-# UNPACK #-} !LocalSendPortId
270413 }
271− deriving (Eq, Ord)
414+ deriving (Eq, Ord, Typeable, Generic)
272415
416+instance Hashable SendPortId where
273417instance Show SendPortId where
274418 show (SendPortId (ProcessId (NodeId addr) (LocalProcessId _ plid)) clid)
275419 = "cid://" ++ show addr ++ ":" ++ show plid ++ ":" ++ show clid
276420
421+instance NFData SendPortId where
422+ rnf (SendPortId p _) = rnf p `seq` ()
423+
277424data TypedChannel = forall a. Serializable a => TypedChannel (Weak (TQueue a))
278425
279426-- | The send send of a typed channel (serializable)
@@ -281,7 +428,11 @@ newtype SendPort a = SendPort {
281428 -- | The (unique) ID of this send port
282429 sendPortId :: SendPortId
283430 }
284− deriving (Typeable, Binary, Show, Eq, Ord)
431+ deriving (Typeable, Generic, Show, Eq, Ord)
432+
433+instance (Serializable a) => Binary (SendPort a) where
434+instance (Hashable a) => Hashable (SendPort a) where
435+instance (NFData a) => NFData (SendPort a) where rnf (SendPort x) = x `seq` ()
285436
286437-- | The receive end of a typed channel (not serializable)
287438--
@@ -319,9 +470,30 @@ data Message =
319470 }
320471 deriving (Typeable)
321472
473+instance NFData Message where
474+#if MIN_VERSION_bytestring(0,10,0)
475+ rnf (EncodedMessage _ e) = rnf e `seq` ()
476+#else
477+ rnf (EncodedMessage _ e) = BSL.length e `seq` ()
478+#endif
479+ rnf (UnencodedMessage _ a) = e `seq` ()
480+ where e = BSL.length (encode a)
481+
322482instance Show Message where
323483 show (EncodedMessage fp enc) = show enc ++ " :: " ++ showFingerprint fp []
324− show (UnencodedMessage fp _) = "[unencoded message] :: " ++ (showFingerprint fp [])
484+ show (UnencodedMessage _ uenc) = "[unencoded message] :: " ++ (show $ typeOf uenc)
485+
486+-- | /internal use only/.
487+isEncoded :: Message -> Bool
488+isEncoded (EncodedMessage _ _) = True
489+isEncoded _ = False
490+-- [note] isEncoded:
491+-- This is just as internal as it looks and yes, it does feel a bit odd that
492+-- we're exporting it for use, however DPP does a /lot/ of work with low
493+-- level APIs such as unwrapMessage and handleMessage, and in the process
494+-- tries very hard to avoid copying (and re-serialisation) where possible.
495+-- Being able to determine that a message is encoded (or otherwise) makes
496+-- that a lot more manageable.
325497
326498-- | Turn any serialiable term into a message
327499createMessage :: Serializable a => a -> Message
@@ -332,6 +504,11 @@ createUnencodedMessage :: Serializable a => a -> Message
332504createUnencodedMessage a =
333505 let encoded = encode a in BSL.length encoded `seq` UnencodedMessage (fingerprint a) a
334506
507+-- | Turn any serializable term into an unencodede/local message, without
508+-- evalutaing it! This is a dangerous business.
509+unsafeCreateUnencodedMessage :: Serializable a => a -> Message
510+unsafeCreateUnencodedMessage a = UnencodedMessage (fingerprint a) a
511+
335512-- | Serialize a message
336513messageToPayload :: Message -> [BSS.ByteString]
337514messageToPayload (EncodedMessage fp enc) = encodeFingerprint fp : BSL.toChunks enc
@@ -364,7 +541,11 @@ data MonitorRef = MonitorRef
364541 -- | Unique to distinguish multiple monitor requests by the same process
365542 , monitorRefCounter :: !Int32
366543 }
367− deriving (Eq, Ord, Show)
544+ deriving (Eq, Ord, Show, Typeable, Generic)
545+instance Hashable MonitorRef
546+
547+instance NFData MonitorRef where
548+ rnf (MonitorRef i _) = rnf i `seq` ()
368549
369550-- | Message sent by process monitors
370551data ProcessMonitorNotification =
@@ -398,9 +579,11 @@ data PortLinkException =
398579
399580-- | Exception thrown when a process attempts to register
400581-- a process under an already-registered name or to
401−-- unregister a name that hasn't been registered
582+-- unregister a name that hasn't been registered. Returns
583+-- the name and the identifier of the process that owns it,
584+-- if any.
402585data ProcessRegistrationException =
403− ProcessRegistrationException !String
586+ ProcessRegistrationException !String !(Maybe ProcessId)
404587 deriving (Typeable, Show)
405588
406589-- | Internal exception thrown indirectly by 'exit'
@@ -412,9 +595,6 @@ instance Exception ProcessExitException
412595instance Show ProcessExitException where
413596 show (ProcessExitException pid _) = "exit-from=" ++ (show pid)
414597
415−exitSource :: ProcessExitException -> ProcessId
416−exitSource (ProcessExitException from _) = from
417−
418598instance Exception ProcessLinkException
419599instance Exception NodeLinkException
420600instance Exception PortLinkException
@@ -435,6 +615,10 @@ data DiedReason =
435615 | DiedUnknownId
436616 deriving (Show, Eq)
437617
618+instance NFData DiedReason where
619+ rnf (DiedException s) = rnf s `seq` ()
620+ rnf x = x `seq` ()
621+
438622-- | (Asynchronous) reply from unmonitor
439623newtype DidUnmonitor = DidUnmonitor MonitorRef
440624 deriving (Typeable, Binary)
@@ -453,7 +637,7 @@ newtype DidUnlinkPort = DidUnlinkPort SendPortId
453637
454638-- | 'SpawnRef' are used to return pids of spawned processes
455639newtype SpawnRef = SpawnRef Int32
456− deriving (Show, Binary, Typeable, Eq)
640+ deriving (Show, Binary, Typeable, Eq, Ord)
457641
458642-- | (Asynchronius) reply from 'spawn'
459643data DidSpawn = DidSpawn SpawnRef ProcessId
@@ -464,14 +648,23 @@ data WhereIsReply = WhereIsReply String (Maybe ProcessId)
464648 deriving (Show, Typeable)
465649
466650-- | (Asynchronous) reply from 'register' and 'unregister'
467−data RegisterReply = RegisterReply String Bool
651+data RegisterReply = RegisterReply String Bool (Maybe ProcessId)
468652 deriving (Show, Typeable)
469653
654+data NodeStats = NodeStats {
655+ nodeStatsNode :: NodeId
656+ , nodeStatsRegisteredNames :: Int
657+ , nodeStatsMonitors :: Int
658+ , nodeStatsLinks :: Int
659+ , nodeStatsProcesses :: Int
660+ }
661+ deriving (Show, Eq, Typeable)
662+
470663-- | Provide information about a running process
471664data ProcessInfo = ProcessInfo {
472665 infoNode :: NodeId
473666 , infoRegisteredNames :: [String]
474− , infoMessageQueueLength :: Maybe Int
667+ , infoMessageQueueLength :: Int
475668 , infoMonitors :: [(ProcessId, MonitorRef)]
476669 , infoLinks :: [ProcessId]
477670 } deriving (Show, Eq, Typeable)
@@ -501,12 +694,14 @@ data ProcessSignal =
501694 | WhereIs !String
502695 | Register !String !NodeId !(Maybe ProcessId) !Bool -- Use 'Nothing' to unregister, use True to force reregister
503696 | NamedSend !String !Message
697+ | UnreliableSend !LocalProcessId !Message
504698 | LocalSend !ProcessId !Message
505699 | LocalPortSend !SendPortId !Message
506700 | Kill !ProcessId !String
507701 | Exit !ProcessId !Message
508702 | GetInfo !ProcessId
509703 | SigShutdown
704+ | GetNodeStats !NodeId
510705 deriving Show
511706
512707--------------------------------------------------------------------------------
@@ -521,10 +716,6 @@ instance Binary LocalProcessId where
521716 put lpid = put (lpidUnique lpid) >> put (lpidCounter lpid)
522717 get = LocalProcessId <$> get <*> get
523718
524−instance Binary ProcessId where
525− put pid = put (processNodeId pid) >> put (processLocalId pid)
526− get = ProcessId <$> get <*> get
527−
528719instance Binary ProcessMonitorNotification where
529720 put (ProcessMonitorNotification ref pid reason) = put ref >> put pid >> put reason
530721 get = ProcessMonitorNotification <$> get <*> get <*> get
@@ -557,10 +748,12 @@ instance Binary ProcessSignal where
557748 put (NamedSend label msg) = putWord8 8 >> put label >> put (messageToPayload msg)
558749 put (Kill pid reason) = putWord8 9 >> put pid >> put reason
559750 put (Exit pid reason) = putWord8 10 >> put pid >> put (messageToPayload reason)
560− put (LocalSend pid msg) = putWord8 11 >> put pid >> put (messageToPayload msg)
751+ put (LocalSend to' msg) = putWord8 11 >> put to' >> put (messageToPayload msg)
561752 put (LocalPortSend sid msg) = putWord8 12 >> put sid >> put (messageToPayload msg)
753+ put (UnreliableSend lpid msg) = putWord8 13 >> put lpid >> put (messageToPayload msg)
562754 put (GetInfo about) = putWord8 30 >> put about
563755 put (SigShutdown) = putWord8 31
756+ put (GetNodeStats nid) = putWord8 32 >> put nid
564757 get = do
565758 header <- getWord8
566759 case header of
@@ -577,8 +770,10 @@ instance Binary ProcessSignal where
577770 10 -> Exit <$> get <*> (payloadToMessage <$> get)
578771 11 -> LocalSend <$> get <*> (payloadToMessage <$> get)
579772 12 -> LocalPortSend <$> get <*> (payloadToMessage <$> get)
773+ 13 -> UnreliableSend <$> get <*> (payloadToMessage <$> get)
580774 30 -> GetInfo <$> get
581775 31 -> return SigShutdown
776+ 32 -> GetNodeStats <$> get
582777 _ -> fail "ProcessSignal.get: invalid"
583778
584779instance Binary DiedReason where
@@ -622,8 +817,8 @@ instance Binary WhereIsReply where
622817 get = WhereIsReply <$> get <*> get
623818
624819instance Binary RegisterReply where
625− put (RegisterReply label ok) = put label >> put ok
626− get = RegisterReply <$> get <*> get
820+ put (RegisterReply label ok owner) = put label >> put ok >> put owner
821+ get = RegisterReply <$> get <*> get <*> get
627822
628823instance Binary ProcessInfo where
629824 get = ProcessInfo <$> get <*> get <*> get <*> get <*> get
@@ -633,6 +828,14 @@ instance Binary ProcessInfo where
633828 >> put (infoMonitors pInfo)
634829 >> put (infoLinks pInfo)
635830
831+instance Binary NodeStats where
832+ get = NodeStats <$> get <*> get <*> get <*> get <*> get
833+ put nStats = put (nodeStatsNode nStats)
834+ >> put (nodeStatsRegisteredNames nStats)
835+ >> put (nodeStatsMonitors nStats)
836+ >> put (nodeStatsLinks nStats)
837+ >> put (nodeStatsProcesses nStats)
838+
636839instance Binary ProcessInfoNone where
637840 get = ProcessInfoNone <$> get
638841 put (ProcessInfoNone r) = put r
@@ -641,23 +844,23 @@ instance Binary ProcessInfoNone where
641844-- Accessors --
642845--------------------------------------------------------------------------------
643846
644−localProcesses :: Accessor LocalNodeState (Map LocalProcessId LocalProcess)
847+localProcesses :: Accessor ValidLocalNodeState (Map LocalProcessId LocalProcess)
645848localProcesses = accessor _localProcesses (\procs st -> st { _localProcesses = procs })
646849
647−localPidCounter :: Accessor LocalNodeState Int32
850+localPidCounter :: Accessor ValidLocalNodeState Int32
648851localPidCounter = accessor _localPidCounter (\ctr st -> st { _localPidCounter = ctr })
649852
650−localPidUnique :: Accessor LocalNodeState Int32
853+localPidUnique :: Accessor ValidLocalNodeState Int32
651854localPidUnique = accessor _localPidUnique (\unq st -> st { _localPidUnique = unq })
652855
653−localConnections :: Accessor LocalNodeState (Map (Identifier, Identifier) (NT.Connection, ImplicitReconnect))
856+localConnections :: Accessor ValidLocalNodeState (Map (Identifier, Identifier) (NT.Connection, ImplicitReconnect))
654857localConnections = accessor _localConnections (\conns st -> st { _localConnections = conns })
655858
656−localProcessWithId :: LocalProcessId -> Accessor LocalNodeState (Maybe LocalProcess)
859+localProcessWithId :: LocalProcessId -> Accessor ValidLocalNodeState (Maybe LocalProcess)
657860localProcessWithId lpid = localProcesses >>> DAC.mapMaybe lpid
658861
659−localConnectionBetween :: Identifier -> Identifier -> Accessor LocalNodeState (Maybe (NT.Connection, ImplicitReconnect))
660−localConnectionBetween from to = localConnections >>> DAC.mapMaybe (from, to)
862+localConnectionBetween :: Identifier -> Identifier -> Accessor ValidLocalNodeState (Maybe (NT.Connection, ImplicitReconnect))
863+localConnectionBetween from' to' = localConnections >>> DAC.mapMaybe (from', to')
661864
662865monitorCounter :: Accessor LocalProcessState Int32
663866monitorCounter = accessor _monitorCounter (\cnt st -> st { _monitorCounter = cnt })
@@ -682,4 +885,3 @@ typedChannelWithId cid = typedChannels >>> DAC.mapMaybe cid
682885{-# INLINE forever' #-}
683886forever' :: Monad m => m a -> m b
684887forever' a = let a' = a >> a' in a'
685−
src/Control/Distributed/Process/Internal/WeakTQueue.hs+9−3
@@ -1,10 +1,12 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveDataTypeable #-}
3+{-# LANGUAGE MagicHash, UnboxedTuples #-}
14-- | Clone of Control.Concurrent.STM.TQueue with support for mkWeakTQueue
25--
36-- Not all functionality from the original module is available: unGetTQueue,
47-- peekTQueue and tryPeekTQueue are missing. In order to implement these we'd
58-- need to be able to touch# the write end of the queue inside unGetTQueue, but
69-- that means we need a version of touch# that works within the STM monad.
7−{-# LANGUAGE MagicHash, UnboxedTuples #-}
810module Control.Distributed.Process.Internal.WeakTQueue (
911 -- * Original functionality
1012 TQueue,
@@ -21,8 +23,8 @@ module Control.Distributed.Process.Internal.WeakTQueue (
2123import Prelude hiding (read)
2224import GHC.Conc
2325import Data.Typeable (Typeable)
24−import GHC.IO (IO(IO))
25−import GHC.Prim (mkWeak#)
26+import GHC.IO (IO(IO), unIO)
27+import GHC.Exts (mkWeak#)
2628import GHC.Weak (Weak(Weak))
2729
2830--------------------------------------------------------------------------------
@@ -98,4 +100,8 @@ isEmptyTQueue (TQueue read write) = do
98100
99101mkWeakTQueue :: TQueue a -> IO () -> IO (Weak (TQueue a))
100102mkWeakTQueue 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
101106 case mkWeak# write# q f s of (# s', w #) -> (# s', Weak w #)
107+#endif
src/Control/Distributed/Process/Management.hsnew file+509
@@ -0,0 +1,509 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE StandaloneDeriving #-}
4+{-# LANGUAGE ExistentialQuantification #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+{-# LANGUAGE RecordWildCards #-}
7+{-# LANGUAGE PatternGuards #-}
8+{-# LANGUAGE FlexibleInstances #-}
9+{-# LANGUAGE UndecidableInstances #-}
10+
11+-----------------------------------------------------------------------------
12+-- |
13+-- Module : Control.Distributed.Process.Management
14+-- Copyright : (c) Well-Typed / Tim Watson
15+-- License : BSD3 (see the file LICENSE)
16+--
17+-- Maintainer : Tim Watson <watson.timothy@gmail.com>
18+-- Stability : experimental
19+-- Portability : non-portable (requires concurrency)
20+--
21+-- [Management Extensions API]
22+--
23+-- This module presents an API for creating /Management Agents/:
24+-- special processes that are capable of receiving and responding to
25+-- a node's internal system events. These /system events/ are delivered by
26+-- the management event bus: An internal subsystem maintained for each
27+-- running node, to which all agents are automatically subscribed.
28+--
29+-- /Agents/ are defined in terms of /event sinks/, taking a particular
30+-- @Serializable@ type and evaluating to an action in the 'MxAgent' monad in
31+-- response. Each 'MxSink' evaluates to an 'MxAction' that specifies whether
32+-- the agent should continue processing it's inputs or stop. If the type of a
33+-- message cannot be matched to any of the agent's sinks, it will be discarded.
34+-- A sink can also deliberately skip processing a message, deferring to the
35+-- remaining handlers. This is the /only/ way that more than one event sink
36+-- can handle the same data type, since otherwise the first /type match/ will
37+-- /win/ every time a message arrives. See 'mxSkip' for details.
38+--
39+-- Various events are published to the management event bus automatically,
40+-- the full list of which can be found in the definition of the 'MxEvent' data
41+-- type. Additionally, clients of the /Management API/ can publish arbitrary
42+-- @Serializable@ data to the event bus using 'mxNotify'. All running agents
43+-- receive all events (from the primary event bus to which they're subscribed).
44+--
45+-- Agent processes are automatically registered on the local node, and can
46+-- receive messages via their mailbox just like ordinary processes. Unlike
47+-- ordinary @Process@ code however, it is unnecessary (though possible) for
48+-- agents to use the base @expect@ and @receiveX@ primitives to do this, since
49+-- the management infrastructure will continuously read from both the primary
50+-- event bus /and/ the process' own mailbox. Messages are transparently passed
51+-- to the agent's event sinks from both sources, so an agent need only concern
52+-- itself with how to respond to its inputs.
53+--
54+-- Some agents may wish to prioritise messages from their mailbox over traffic
55+-- on the management event bus, or vice versa. The 'mxReceive' and
56+-- 'mxReceiveChan' API calls do this for the mailbox and event bus,
57+-- respectively. The /prioritisation/ these APIs offer is simply that the chosen
58+-- data stream will be checked first. No blocking will occur if the chosen
59+-- (prioritised) source is devoid of input messages, instead the agent handling
60+-- code will revert to switching between the alternatives in /round-robin/ as
61+-- usual. If messages exist in one or more channels, they will be consumed as
62+-- soon as they're available, priority is effectively a hint about which
63+-- channel to consume from, should messages be available in both.
64+--
65+-- Prioritisation then, is a /hint/ about the preference of data source from
66+-- which the next input should be chosen. No guarantee can be made that the
67+-- chosen source will in fact be selected at runtime.
68+--
69+-- [Management API Semantics]
70+--
71+-- The management API provides /no guarantees whatsoever/, viz:
72+--
73+-- * The ordering of messages delivered to the event bus.
74+--
75+-- * The order in which agents will be executed.
76+--
77+-- * Whether messages will be taken from the mailbox first, or the event bus.
78+--
79+-- Since the event bus uses STM broadcast channels to communicate with agents,
80+-- no message written to the bus successfully can be lost.
81+--
82+-- Agents can also receive messages via their mailboxes - these are subject to
83+-- the same guarantees as all inter-process message sending.
84+--
85+-- Messages dispatched on an STM broadcast channel (i.e., management event bus)
86+-- are guaranteed to be delivered with the same FIFO ordering guarantees that
87+-- exist between two communicating processes, such that communication from the
88+-- node controller's threads (i.e., MxEvent's) will never be re-ordered, but
89+-- messages dispatched to the event bus by other processes (including, but not
90+-- limited to agents) are only guaranteed to be ordered between one sender and
91+-- one receiver.
92+--
93+-- No guarantee exists for the ordering in which messages sent to an agent's
94+-- mailbox will be delivered, vs messages dispatched via the event bus.
95+--
96+-- Because of the above, there are no ordering guarantees for messages sent
97+-- between agents, or for processes to agents, except for those that apply to
98+-- messages sent between regular processes, since agents are
99+-- implemented as such.
100+--
101+-- The event bus is serial and single threaded. Anything that is published by
102+-- the node controller will be seen in FIFO order. There are no ordering
103+-- guarantees pertaining to entries published to the event bus by other
104+-- processes or agents.
105+--
106+-- It should not be possible to see, for example, an @MxReceived@ before the
107+-- corresponding @MxSent@ event, since the places where we issue the @MxSent@
108+-- write directly to the event bus (using STM) in the calling (green) thread,
109+-- before dispatching instructions to the node controller to perform the
110+-- necessary routing to deliver the message to a process (or registered name,
111+-- or typed channel) locally or remotely.
112+--
113+-- [Management Data API]
114+--
115+-- Both management agents and clients of the API have access to a variety of
116+-- data storage capabilities, to facilitate publishing and consuming useful
117+-- system information. Agents maintain their own internal state privately (via a
118+-- state transformer - see 'mxGetLocal' et al), however it is possible for
119+-- agents to share additional data with each other (and the outside world)
120+-- using whatever mechanism the user wishes, e.g., acidstate, or shared memory
121+-- primitives.
122+--
123+-- [Defining Agents]
124+--
125+-- New agents are defined with 'mxAgent' and require a unique 'MxAgentId', an
126+-- initial state - 'MxAgent' runs in a state transformer - and a list of the
127+-- agent's event sinks. Each 'MxSink' is defined in terms of a specific
128+-- @Serializable@ type, via the 'mxSink' function, binding the event handler
129+-- expression to inputs of only that type.
130+--
131+-- Apart from modifying its own local state, an agent can execute arbitrary
132+-- @Process a@ code via lifting (see 'liftMX') and even publish its own messages
133+-- back to the primary event bus (see 'mxBroadcast').
134+--
135+-- Since messages are delivered to agents from both the management event bus and
136+-- the agent processes mailbox, agents (i.e., event sinks) will generally have
137+-- no idea as to their origin. An agent can, however, choose to prioritise the
138+-- choice of input (source) each time one of its event sinks runs. The /standard/
139+-- way for an event sink to indicate that the agent is ready for its next input
140+-- is to evaluate 'mxReady'. When this happens, the management infrastructure
141+-- will obtain data from the event bus and process' mailbox in a round robbin
142+-- fashion, i.e., one after the other, changing each time.
143+--
144+-- [Example Code]
145+--
146+-- What follows is a grossly over-simplified example of a management agent that
147+-- provides a basic name monitoring facility. Whenever a process name is
148+-- registered or unregistered, clients are informed of the fact.
149+--
150+-- > -- simple notification data type
151+-- >
152+-- > data Registration = Reg { added :: Bool
153+-- > , procId :: ProcessId
154+-- > , name :: String
155+-- > }
156+-- >
157+-- > -- start a /name monitoring agent/
158+-- > nameMonitorAgent = do
159+-- > mxAgent (MxAgentId "name-monitor") Set.empty [
160+-- > (mxSink $ \(pid :: ProcessId) -> do
161+-- > mxUpdateState $ Set.insert pid
162+-- > mxReady)
163+-- > , (mxSink $
164+-- > let act =
165+-- > case ev of
166+-- > (MxRegistered p n) -> notify True n p
167+-- > (MxUnRegistered p n) -> notify False n p
168+-- > _ -> return ()
169+-- > act >> mxReady)
170+-- > ]
171+-- > where
172+-- > notify a n p = do
173+-- > Foldable.mapM_ (liftMX . deliver (Reg a n p)) =<< mxGetLocal
174+-- >
175+--
176+-- The client interface (for sending their pid) can take one of two forms:
177+--
178+-- > monitorNames = getSelfPid >>= nsend "name-monitor"
179+-- > monitorNames2 = getSelfPid >>= mxNotify
180+--
181+-- [Performance, Stablity and Scalability]
182+--
183+-- /Management Agents/ offer numerous advantages over regular processes:
184+-- broadcast communication with them can have a lower latency, they offer
185+-- simplified messgage (i.e., input type) handling and they have access to
186+-- internal system information that would be otherwise unobtainable.
187+--
188+-- Do not be tempted to implement everything (e.g., the kitchen sink) using the
189+-- management API though. There are overheads associated with management agents
190+-- which is why they're presented as tools for consuming low level system
191+-- information, instead of as /application level/ development tools.
192+--
193+-- Agents that rely heavily on a busy mailbox can cause the management event
194+-- bus to backlog un-GC'ed data, leading to increased heap space. Producers that
195+-- do not take care to avoid passing unevaluated thunks to the API can crash
196+-- /all/ the agents in the system. Agents are not monitored or managed in any
197+-- way, and those that crash will not be restarted.
198+--
199+-- The management event bus can receive a great deal of traffic. Every time
200+-- a message is sent and/or received, an event is passed to the agent controller
201+-- and broadcast to all agents (plus the trace controller, if tracing is enabled
202+-- for the node). This is already a significant overhead - though profiling and
203+-- benchmarks have demonstrated that it does not adversely affect performance
204+-- if few agents are installed. Agents will typically use more cycles than plain
205+-- processes, since they perform additional work: selecting input data from both
206+-- the event bus /and/ their own mailboxes, plus searching through the set of
207+-- event sinks (for each agent) to determine the right handler for the event.
208+--
209+-- [Architecture Overview]
210+--
211+-- The architecture of the management event bus is internal and subject to
212+-- change without prior notice. The description that follows is provided for
213+-- informational purposes only.
214+--
215+-- When a node initially starts, two special, internal system processes are
216+-- started to support the management infrastructure. The first, known as the
217+-- /trace controller/, is responsible for consuming 'MxEvent's and forwarding
218+-- them to the configured tracer - see "Control.Distributed.Process.Debug" for
219+-- further details. The second is the /management agent controller/, and is the
220+-- primary worker process underpinning the management infrastructure. All
221+-- published management events are routed to this process, which places them
222+-- onto a system wide /event bus/ and additionally passes them directly to the
223+-- /trace controller/.
224+--
225+-- There are several reasons for segregating the tracing and management control
226+-- planes in this fashion. Tracing can be enabled or disabled by clients, whilst
227+-- the management event bus cannot, since in addition to providing
228+-- runtime instrumentation, its intended use-cases include node monitoring, peer
229+-- discovery (via topology providing backends) and other essential system
230+-- services that require knowledge of otherwise hidden system internals. Tracing
231+-- is also subject to /trace flags/ that limit the specific 'MxEvent's delivered
232+-- to trace clients - an overhead/complexity not shared by management agents.
233+-- Finally, tracing and management agents are implemented using completely
234+-- different signalling techniques - more on this later - which would introduce
235+-- considerable complexity if the shared the same /event loop/.
236+--
237+-- The management control plane is driven by a shared broadcast channel, which
238+-- is written to by the agent controller and subscribed to by all agent
239+-- processes. Agents are spawned as regular processes, whose primary
240+-- implementation (i.e., /server loop/) is responsible for consuming
241+-- messages from both the broadcast channel and their own mailbox. Once
242+-- consumed, messages are applied to the agent's /event sinks/ until one
243+-- matches the input, at which point it is applied and the loop continues.
244+-- The implementation chooses from the event bus and the mailbox in a
245+-- round-robin fashion, until a message is received. This polling activity would
246+-- lead to management agents consuming considerable system resources if left
247+-- unchecked, therefore the implementation will poll for a limitted number of
248+-- retries, after which it will perform a blocking read on the event bus.
249+--
250+-----------------------------------------------------------------------------
251+module Control.Distributed.Process.Management
252+ (
253+ MxEvent(..)
254+ -- * Firing Arbitrary /Mx Events/
255+ , mxNotify
256+ -- * Constructing Mx Agents
257+ , MxAction()
258+ , MxAgentId(..)
259+ , MxAgent()
260+ , mxAgent
261+ , mxAgentWithFinalize
262+ , MxSink()
263+ , mxSink
264+ , mxGetId
265+ , mxDeactivate
266+ , mxReady
267+ , mxSkip
268+ , mxReceive
269+ , mxReceiveChan
270+ , mxBroadcast
271+ , mxSetLocal
272+ , mxGetLocal
273+ , mxUpdateLocal
274+ , liftMX
275+ ) where
276+
277+import Control.Applicative
278+import Control.Concurrent.STM (atomically)
279+import Control.Concurrent.STM.TChan
280+ ( readTChan
281+ , writeTChan
282+ , TChan
283+ )
284+import Control.Distributed.Process.Internal.Primitives
285+ ( receiveWait
286+ , matchAny
287+ , matchSTM
288+ , unwrapMessage
289+ , register
290+ , whereis
291+ , die
292+ )
293+import Control.Distributed.Process.Internal.Types
294+ ( Process
295+ , ProcessId
296+ , Message
297+ , LocalProcess(..)
298+ , LocalNode(..)
299+ , MxEventBus(..)
300+ , unsafeCreateUnencodedMessage
301+ )
302+import Control.Distributed.Process.Management.Internal.Bus (publishEvent)
303+import Control.Distributed.Process.Management.Internal.Types
304+ ( MxAgentId(..)
305+ , MxAgent(..)
306+ , MxAction(..)
307+ , ChannelSelector(..)
308+ , MxAgentState(..)
309+ , MxSink
310+ , MxEvent(..)
311+ )
312+import Control.Distributed.Process.Serializable (Serializable)
313+import Control.Monad.IO.Class (liftIO)
314+import Control.Monad.Reader (ask)
315+import Control.Monad.Catch (onException)
316+import qualified Control.Monad.State as ST
317+ ( get
318+ , modify
319+ , lift
320+ , runStateT
321+ )
322+import Prelude
323+
324+-- | Publishes an arbitrary @Serializable@ message to the management event bus.
325+-- Note that /no attempt is made to force the argument/, therefore it is very
326+-- important that you do not pass unevaluated thunks that might crash the
327+-- receiving process via this API, since /all/ registered agents will gain
328+-- access to the data structure once it is broadcast by the agent controller.
329+mxNotify :: (Serializable a) => a -> Process ()
330+mxNotify msg = do
331+ bus <- localEventBus . processNode <$> ask
332+ liftIO $ publishEvent bus $ unsafeCreateUnencodedMessage msg
333+
334+--------------------------------------------------------------------------------
335+-- API for writing user defined management extensions (i.e., agents) --
336+--------------------------------------------------------------------------------
337+
338+-- | Return the 'MxAgentId' for the currently executing agent.
339+--
340+mxGetId :: MxAgent s MxAgentId
341+mxGetId = ST.get >>= return . mxAgentId
342+
343+-- | The 'MxAgent' version of 'mxNotify'.
344+--
345+mxBroadcast :: (Serializable m) => m -> MxAgent s ()
346+mxBroadcast msg = do
347+ state <- ST.get
348+ liftMX $ liftIO $ atomically $ do
349+ writeTChan (mxBus state) (unsafeCreateUnencodedMessage msg)
350+
351+-- | Gracefully terminate an agent.
352+--
353+mxDeactivate :: forall s. String -> MxAgent s MxAction
354+mxDeactivate = return . MxAgentDeactivate
355+
356+-- | Continue executing (i.e., receiving and processing messages).
357+--
358+mxReady :: forall s. MxAgent s MxAction
359+mxReady = return MxAgentReady
360+
361+-- | Causes the currently executing /event sink/ to be skipped.
362+-- The remaining declared event sinks will be evaluated to find
363+-- a matching handler. Can be used to allow multiple event sinks
364+-- to process data of the same type.
365+--
366+mxSkip :: forall s. MxAgent s MxAction
367+mxSkip = return MxAgentSkip
368+
369+-- | Continue exeucting, prioritising inputs from the process' own
370+-- /mailbox/ ahead of data from the management event bus.
371+--
372+mxReceive :: forall s. MxAgent s MxAction
373+mxReceive = return $ MxAgentPrioritise Mailbox
374+
375+-- | Continue exeucting, prioritising inputs from the management event bus
376+-- over the process' own /mailbox/.
377+--
378+mxReceiveChan :: forall s. MxAgent s MxAction
379+mxReceiveChan = return $ MxAgentPrioritise InputChan
380+
381+-- | Lift a @Process@ action.
382+--
383+liftMX :: Process a -> MxAgent s a
384+liftMX p = MxAgent $ ST.lift p
385+
386+-- | Set the agent's local state.
387+--
388+mxSetLocal :: s -> MxAgent s ()
389+mxSetLocal s = ST.modify $ \st -> st { mxLocalState = s }
390+
391+-- | Update the agent's local state.
392+--
393+mxUpdateLocal :: (s -> s) -> MxAgent s ()
394+mxUpdateLocal f = ST.modify $ \st -> st { mxLocalState = (f $ mxLocalState st) }
395+
396+-- | Fetch the agent's local state.
397+--
398+mxGetLocal :: MxAgent s s
399+mxGetLocal = ST.get >>= return . mxLocalState
400+
401+-- | Create an 'MxSink' from an expression taking a @Serializable@ type @m@,
402+-- that yields an 'MxAction' in the 'MxAgent' monad.
403+--
404+mxSink :: forall s m . (Serializable m)
405+ => (m -> MxAgent s MxAction)
406+ -> MxSink s
407+mxSink act msg = do
408+ msg' <- liftMX $ (unwrapMessage msg :: Process (Maybe m))
409+ case msg' of
410+ Nothing -> return Nothing
411+ Just m -> do
412+ r <- act m
413+ case r of
414+ MxAgentSkip -> return Nothing
415+ _ -> return $ Just r
416+
417+-- private ADT: a linked list of event sinks
418+data MxPipeline s =
419+ MxPipeline
420+ {
421+ current :: !(MxSink s)
422+ , next :: !(MxPipeline s)
423+ } | MxStop
424+
425+-- | Activates a new agent.
426+--
427+mxAgent :: MxAgentId -> s -> [MxSink s] -> Process ProcessId
428+mxAgent mxId st hs = mxAgentWithFinalize mxId st hs $ return ()
429+
430+-- | Activates a new agent. This variant takes a /finalizer/ expression,
431+-- that is run once the agent shuts down (even in case of failure/exceptions).
432+-- The /finalizer/ expression runs in the mx monad - @MxAgent s ()@ - such
433+-- that the agent's internal state remains accessible to the shutdown/cleanup
434+-- code.
435+--
436+mxAgentWithFinalize :: MxAgentId
437+ -> s
438+ -> [MxSink s]
439+ -> MxAgent s ()
440+ -> Process ProcessId
441+mxAgentWithFinalize mxId initState handlers dtor = do
442+ let name = agentId mxId
443+ existing <- whereis name
444+ case existing of
445+ Just _ -> die "DuplicateAgentId" -- TODO: better error handling policy
446+ Nothing -> do
447+ node <- processNode <$> ask
448+ pid <- liftIO $ mxNew (localEventBus node) $ start
449+ register name pid
450+ return pid
451+ where
452+ start (sendTChan, recvTChan) = do
453+ let nState = MxAgentState mxId sendTChan initState
454+ runAgent dtor handlers InputChan recvTChan nState
455+
456+ runAgent :: MxAgent s ()
457+ -> [MxSink s]
458+ -> ChannelSelector
459+ -> TChan Message
460+ -> MxAgentState s
461+ -> Process ()
462+ runAgent eh hs cs c s =
463+ runAgentWithFinalizer eh hs cs c s
464+ `onException` runAgentFinalizer eh s
465+
466+ runAgentWithFinalizer :: MxAgent s ()
467+ -> [MxSink s]
468+ -> ChannelSelector
469+ -> TChan Message
470+ -> MxAgentState s
471+ -> Process ()
472+ runAgentWithFinalizer eh' hs' cs' c' s' = do
473+ msg <- getNextInput cs' c'
474+ (action, state) <- runPipeline msg s' $ pipeline hs'
475+ case action of
476+ MxAgentReady -> runAgent eh' hs' InputChan c' state
477+ MxAgentPrioritise priority -> runAgent eh' hs' priority c' state
478+ MxAgentDeactivate _ -> runAgentFinalizer eh' state
479+ MxAgentSkip -> error "IllegalState"
480+-- MxAgentBecome h' -> runAgent h' c state
481+
482+ getNextInput sel chan =
483+ let matches =
484+ case sel of
485+ Mailbox -> [ matchAny return
486+ , matchSTM (readTChan chan) return]
487+ InputChan -> [ matchSTM (readTChan chan) return
488+ , matchAny return]
489+ in receiveWait matches
490+
491+ runAgentFinalizer :: MxAgent s () -> MxAgentState s -> Process ()
492+ runAgentFinalizer f s = ST.runStateT (unAgent f) s >>= return . fst
493+
494+ pipeline :: forall s . [MxSink s] -> MxPipeline s
495+ pipeline [] = MxStop
496+ pipeline (sink:sinks) = MxPipeline sink (pipeline sinks)
497+
498+ runPipeline :: forall s .
499+ Message
500+ -> MxAgentState s
501+ -> MxPipeline s
502+ -> Process (MxAction, MxAgentState s)
503+ runPipeline _ state MxStop = return (MxAgentReady, state)
504+ runPipeline msg state MxPipeline{..} = do
505+ let act = current msg
506+ (pass, state') <- ST.runStateT (unAgent act) state
507+ case pass of
508+ Nothing -> runPipeline msg state next
509+ Just result -> return (result, state')
src/Control/Distributed/Process/Management/Internal/Agent.hsnew file+157
@@ -0,0 +1,157 @@
1+{-# LANGUAGE ScopedTypeVariables #-}
2+{-# LANGUAGE RecordWildCards #-}
3+
4+module Control.Distributed.Process.Management.Internal.Agent where
5+
6+import Control.Applicative
7+import Control.Concurrent (forkIO)
8+import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar)
9+import Control.Concurrent.STM (atomically)
10+import Control.Concurrent.STM.TChan
11+ ( TChan
12+ , newBroadcastTChanIO
13+ , readTChan
14+ , writeTChan
15+ , dupTChan
16+ )
17+import Control.Distributed.Process.Internal.Primitives
18+ ( receiveWait
19+ , matchAny
20+ , die
21+ , catches
22+ , Handler(..)
23+ )
24+import Control.Distributed.Process.Internal.CQueue
25+ ( enqueueSTM
26+ , CQueue
27+ )
28+import Control.Distributed.Process.Management.Internal.Types
29+ ( Fork
30+ )
31+import Control.Distributed.Process.Management.Internal.Trace.Tracer
32+ ( traceController
33+ )
34+import Control.Distributed.Process.Internal.Types
35+ ( Process
36+ , Message
37+ , Tracer(..)
38+ , LocalProcess(..)
39+ , ProcessId
40+ , forever'
41+ )
42+import Control.Exception (AsyncException(ThreadKilled), SomeException)
43+import Control.Monad (void)
44+import Control.Monad.IO.Class (liftIO)
45+import Control.Monad.Reader (ask)
46+import GHC.Weak (Weak, deRefWeak)
47+import Prelude
48+
49+--------------------------------------------------------------------------------
50+-- Agent Controller Implementation --
51+--------------------------------------------------------------------------------
52+
53+-- | A triple containing a configured tracer, weak pointer to the
54+-- agent controller's mailbox (CQueue) and an expression used to
55+-- instantiate new agents on the current node.
56+type AgentConfig =
57+ (Tracer, Weak (CQueue Message),
58+ (((TChan Message, TChan Message) -> Process ()) -> IO ProcessId))
59+
60+-- | Starts a management agent for the current node. The agent process
61+-- must not crash or be killed, so we generally avoid publishing its
62+-- @ProcessId@ where possible.
63+--
64+-- Our process is also responsible for forwarding messages to the trace
65+-- controller, since having two /special processes/ handled via the
66+-- @LocalNode@ would be inelegant. We forward messages directly to the
67+-- trace controller's message queue, just as the @MxEventBus@ that's
68+-- set up on the @LocalNode@ forwards messages directly to us. This
69+-- optimises the code path for tracing and avoids overloading the node
70+-- node controller's internal control plane with additional routing, at the
71+-- cost of a little more complexity and two cases where we break
72+-- encapsulation.
73+--
74+mxAgentController :: Fork
75+ -> MVar AgentConfig
76+ -> Process ()
77+mxAgentController forkProcess mv = do
78+ trc <- liftIO $ startTracing forkProcess
79+ sigbus <- liftIO $ newBroadcastTChanIO
80+ liftIO $ startDeadLetterQueue sigbus
81+ weakQueue <- processWeakQ <$> ask
82+ liftIO $ putMVar mv (trc, weakQueue, mxStartAgent forkProcess sigbus)
83+ go sigbus trc
84+ where
85+ go bus tracer = forever' $ do
86+ void $ receiveWait [
87+ -- This is exactly what it appears to be: a "catch all" handler.
88+ -- Since mxNotify can potentially pass an unevaluated thunk to
89+ -- our mailbox, the dequeue (i.e., matchMessage) can fail and
90+ -- crash this process, which we DO NOT want. Alternatively,
91+ -- we handle IO exceptions here explicitly, since we don't want
92+ -- this process to ever crash, and the assumption we therefore
93+ -- make is thus:
94+ --
95+ -- 1. only ThreadKilled can tell this process to terminate
96+ -- 2. all other exceptions are invalid and should be ignored
97+ --
98+ -- The outcome of course, is that /bad/ calls to mxNotify
99+ -- (e.g., passing unevaluated thunks that will crash when
100+ -- they're eventually forced) are thus silently ignored.
101+ --
102+ matchAny (liftIO . broadcast bus tracer)
103+ ] `catches` [Handler (\ThreadKilled -> die "Killed"),
104+ Handler (\(_ :: SomeException) -> return ())]
105+
106+ broadcast :: TChan Message -> Tracer -> Message -> IO ()
107+ broadcast ch tr msg = do
108+ tmQueue <- tracerQueue tr
109+ atomicBroadcast ch tmQueue msg
110+
111+ tracerQueue :: Tracer -> IO (Maybe (CQueue Message))
112+ tracerQueue (Tracer _ wQ) = deRefWeak wQ
113+
114+ atomicBroadcast :: TChan Message
115+ -> Maybe (CQueue Message)
116+ -> Message -> IO ()
117+ atomicBroadcast ch Nothing msg = liftIO $ atomically $ writeTChan ch msg
118+ atomicBroadcast ch (Just q) msg = do
119+ -- liftIO $ putStrLn $ "broadcasting " ++ (show msg)
120+ liftIO $ atomically $ enqueueSTM q msg >> writeTChan ch msg
121+
122+-- | Forks a new process in which an mxAgent is run.
123+--
124+mxStartAgent :: Fork
125+ -> TChan Message
126+ -> ((TChan Message, TChan Message) -> Process ())
127+ -> IO ProcessId
128+mxStartAgent fork chan handler = do
129+ chan' <- atomically (dupTChan chan)
130+ let proc = handler (chan, chan')
131+ fork proc
132+
133+-- | Start the tracer controller.
134+--
135+startTracing :: Fork -> IO Tracer
136+startTracing forkProcess = do
137+ mv <- newEmptyMVar
138+ pid <- forkProcess $ traceController mv
139+ wQ <- liftIO $ takeMVar mv
140+ return $ Tracer pid wQ
141+
142+-- | Start a dead letter (agent) queue.
143+--
144+-- If no agents are registered on the system, the management
145+-- event bus will fill up and its data won't be GC'ed until someone
146+-- comes along and reads from the broadcast channel (via dupTChan
147+-- of course). This is effectively a leak, so to mitigate it, we
148+-- start a /dead letter queue/ that drains the event bus continuously,
149+-- thus ensuring if there are no other consumers that we won't use
150+-- up heap space unnecessarily.
151+--
152+startDeadLetterQueue :: TChan Message
153+ -> IO ()
154+startDeadLetterQueue sigbus = do
155+ chan' <- atomically (dupTChan sigbus)
156+ void $ forkIO $ forever' $ do
157+ void $ atomically $ readTChan chan'
src/Control/Distributed/Process/Management/Internal/Bus.hsnew file+21
@@ -0,0 +1,21 @@
1+-- | Interface to the management event bus.
2+module Control.Distributed.Process.Management.Internal.Bus
3+ ( publishEvent
4+ ) where
5+
6+import Control.Distributed.Process.Internal.CQueue
7+ ( enqueue
8+ )
9+import Control.Distributed.Process.Internal.Types
10+ ( MxEventBus(..)
11+ , Message
12+ )
13+import Data.Foldable (forM_)
14+import System.Mem.Weak (deRefWeak)
15+
16+publishEvent :: MxEventBus -> Message -> IO ()
17+publishEvent MxEventBusInitialising _ = return ()
18+publishEvent (MxEventBus _ _ wqRef _) msg = do
19+ mQueue <- deRefWeak wqRef
20+ forM_ mQueue $ \queue -> enqueue queue msg
21+
src/Control/Distributed/Process/Management/Internal/Trace/Primitives.hsnew file+179
@@ -0,0 +1,179 @@
1+{-# LANGUAGE FlexibleInstances #-}
2+{-# LANGUAGE TypeSynonymInstances #-}
3+-- | Keeps the tracing API calls separate from the Tracer implementation,
4+-- which allows us to avoid a nasty import cycle between tracing and
5+-- the messaging primitives that rely on it, and also between the node
6+-- controller (which requires access to the tracing related elements of
7+-- our RemoteTable) and the Debug module, which requires @forkProcess@.
8+-- This module is also used by the management agent, which relies on the
9+-- tracing infrastructure's messaging fabric.
10+module Control.Distributed.Process.Management.Internal.Trace.Primitives
11+ ( -- * Sending Trace Data
12+ traceLog
13+ , traceLogFmt
14+ , traceMessage
15+ -- * Configuring A Tracer
16+ , defaultTraceFlags
17+ , enableTrace
18+ , enableTraceAsync
19+ , disableTrace
20+ , disableTraceAsync
21+ , getTraceFlags
22+ , setTraceFlags
23+ , setTraceFlagsAsync
24+ , traceOnly
25+ , traceOn
26+ , traceOff
27+ , withLocalTracer
28+ , withRegisteredTracer
29+ ) where
30+
31+import Control.Applicative
32+import Control.Distributed.Process.Internal.Primitives
33+ ( whereis
34+ , newChan
35+ , receiveChan
36+ , die
37+ )
38+import Control.Distributed.Process.Management.Internal.Trace.Types
39+ ( TraceArg(..)
40+ , TraceFlags(..)
41+ , TraceOk(..)
42+ , TraceSubject(..)
43+ , defaultTraceFlags
44+ )
45+import qualified Control.Distributed.Process.Management.Internal.Trace.Types as Tracer
46+ ( traceLog
47+ , traceLogFmt
48+ , traceMessage
49+ , enableTrace
50+ , enableTraceSync
51+ , disableTrace
52+ , disableTraceSync
53+ , setTraceFlags
54+ , setTraceFlagsSync
55+ , getTraceFlags
56+ , getCurrentTraceClient
57+ )
58+import Control.Distributed.Process.Internal.Types
59+ ( Process
60+ , ProcessId
61+ , LocalProcess(..)
62+ , LocalNode(localEventBus)
63+ , SendPort
64+ , MxEventBus(..)
65+ )
66+import Control.Distributed.Process.Serializable
67+import Control.Monad.IO.Class (liftIO)
68+import Control.Monad.Reader (ask)
69+
70+import qualified Data.Set as Set (fromList)
71+import Prelude
72+
73+--------------------------------------------------------------------------------
74+-- Main API --
75+--------------------------------------------------------------------------------
76+
77+-- | Converts a list of identifiers (that can be
78+-- mapped to process ids), to a 'TraceSubject'.
79+class Traceable a where
80+ uod :: [a] -> TraceSubject
81+
82+instance Traceable ProcessId where
83+ uod = TraceProcs . Set.fromList
84+
85+instance Traceable String where
86+ uod = TraceNames . Set.fromList
87+
88+-- | Turn tracing for for a subset of trace targets.
89+traceOnly :: Traceable a => [a] -> Maybe TraceSubject
90+traceOnly = Just . uod
91+
92+-- | Trace all targets.
93+traceOn :: Maybe TraceSubject
94+traceOn = Just TraceAll
95+
96+-- | Trace no targets.
97+traceOff :: Maybe TraceSubject
98+traceOff = Nothing
99+
100+-- | Enable tracing to the supplied process.
101+enableTraceAsync :: ProcessId -> Process ()
102+enableTraceAsync pid = withLocalTracer $ \t -> liftIO $ Tracer.enableTrace t pid
103+
104+-- TODO: refactor _Sync versions of trace configuration functions...
105+
106+-- | Enable tracing to the supplied process and wait for a @TraceOk@
107+-- response from the trace coordinator process.
108+enableTrace :: ProcessId -> Process ()
109+enableTrace pid =
110+ withLocalTracerSync $ \t sp -> Tracer.enableTraceSync t sp pid
111+
112+-- | Disable the currently configured trace.
113+disableTraceAsync :: Process ()
114+disableTraceAsync = withLocalTracer $ \t -> liftIO $ Tracer.disableTrace t
115+
116+-- | Disable the currently configured trace and wait for a @TraceOk@
117+-- response from the trace coordinator process.
118+disableTrace :: Process ()
119+disableTrace =
120+ withLocalTracerSync $ \t sp -> Tracer.disableTraceSync t sp
121+
122+getTraceFlags :: Process TraceFlags
123+getTraceFlags = do
124+ (sp, rp) <- newChan
125+ withLocalTracer $ \t -> liftIO $ Tracer.getTraceFlags t sp
126+ receiveChan rp
127+
128+-- | Set the given flags for the current tracer.
129+setTraceFlagsAsync :: TraceFlags -> Process ()
130+setTraceFlagsAsync f = withLocalTracer $ \t -> liftIO $ Tracer.setTraceFlags t f
131+
132+-- | Set the given flags for the current tracer and wait for a @TraceOk@
133+-- response from the trace coordinator process.
134+setTraceFlags :: TraceFlags -> Process ()
135+setTraceFlags f =
136+ withLocalTracerSync $ \t sp -> Tracer.setTraceFlagsSync t sp f
137+
138+-- | Send a log message to the internal tracing facility. If tracing is
139+-- enabled, this will create a custom trace log event.
140+--
141+traceLog :: String -> Process ()
142+traceLog s = withLocalTracer $ \t -> liftIO $ Tracer.traceLog t s
143+
144+-- | Send a log message to the internal tracing facility, using the given
145+-- list of printable 'TraceArg's interspersed with the preceding delimiter.
146+--
147+traceLogFmt :: String -> [TraceArg] -> Process ()
148+traceLogFmt d ls = withLocalTracer $ \t -> liftIO $ Tracer.traceLogFmt t d ls
149+
150+-- | Send an arbitrary 'Message' to the tracer process.
151+traceMessage :: Serializable m => m -> Process ()
152+traceMessage msg = withLocalTracer $ \t -> liftIO $ Tracer.traceMessage t msg
153+
154+withLocalTracer :: (MxEventBus -> Process ()) -> Process ()
155+withLocalTracer act = do
156+ node <- processNode <$> ask
157+ act (localEventBus node)
158+
159+withLocalTracerSync :: (MxEventBus -> SendPort TraceOk -> IO ()) -> Process ()
160+withLocalTracerSync act = do
161+ (sp, rp) <- newChan
162+ withLocalTracer $ \t -> liftIO $ (act t sp)
163+ TraceOk <- receiveChan rp
164+ return ()
165+
166+withRegisteredTracer :: (ProcessId -> Process a) -> Process a
167+withRegisteredTracer act = do
168+ (sp, rp) <- newChan
169+ withLocalTracer $ \t -> liftIO $ Tracer.getCurrentTraceClient t sp
170+ currentTracer <- receiveChan rp
171+ case currentTracer of
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)"
179+ (Just p) -> act p
src/Control/Distributed/Process/Management/Internal/Trace/Remote.hsnew file+61
@@ -0,0 +1,61 @@
1+module Control.Distributed.Process.Management.Internal.Trace.Remote
2+ ( -- * Configuring A Remote Tracer
3+ setTraceFlagsRemote
4+ , startTraceRelay
5+ -- * Remote Table
6+ , remoteTable
7+ ) where
8+
9+import Control.Distributed.Process.Internal.Closure.BuiltIn
10+ ( cpEnableTraceRemote
11+ )
12+import Control.Distributed.Process.Internal.Primitives
13+ ( getSelfPid
14+ , relay
15+ , nsendRemote
16+ )
17+import Control.Distributed.Process.Management.Internal.Trace.Types
18+ ( TraceFlags(..)
19+ , TraceOk(..)
20+ )
21+import Control.Distributed.Process.Management.Internal.Trace.Primitives
22+ ( withRegisteredTracer
23+ , enableTrace
24+ )
25+import Control.Distributed.Process.Internal.Spawn
26+ ( spawn
27+ )
28+import Control.Distributed.Process.Internal.Types
29+ ( Process
30+ , ProcessId
31+ , SendPort
32+ , NodeId
33+ )
34+import Control.Distributed.Static
35+ ( RemoteTable
36+ , registerStatic
37+ )
38+import Data.Rank1Dynamic (toDynamic)
39+
40+-- | Remote Table.
41+remoteTable :: RemoteTable -> RemoteTable
42+remoteTable = registerStatic "$enableTraceRemote" (toDynamic enableTraceRemote)
43+
44+enableTraceRemote :: ProcessId -> Process ()
45+enableTraceRemote pid =
46+ getSelfPid >>= enableTrace >> relay pid
47+
48+-- | Starts a /trace relay/ process on the remote node, which forwards all trace
49+-- events to the registered tracer on /this/ (the calling process') node.
50+startTraceRelay :: NodeId -> Process ProcessId
51+startTraceRelay nodeId = do
52+ withRegisteredTracer $ \pid ->
53+ spawn nodeId $ cpEnableTraceRemote pid
54+
55+-- | Set the given flags for a remote node (asynchronous).
56+setTraceFlagsRemote :: TraceFlags -> NodeId -> Process ()
57+setTraceFlagsRemote flags node = do
58+ nsendRemote node
59+ "trace.controller"
60+ ((Nothing :: Maybe (SendPort TraceOk)), flags)
61+
src/Control/Distributed/Process/Management/Internal/Trace/Tracer.hsnew file+354
@@ -0,0 +1,354 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+-- | Tracing/Debugging support - Trace Implementation
4+module Control.Distributed.Process.Management.Internal.Trace.Tracer
5+ ( -- * API for the Management Agent
6+ traceController
7+ -- * Built in tracers
8+ , defaultTracer
9+ , systemLoggerTracer
10+ , logfileTracer
11+ , eventLogTracer
12+ ) where
13+
14+import Control.Applicative
15+import Control.Concurrent.Chan (writeChan)
16+import Control.Concurrent.MVar
17+ ( MVar
18+ , putMVar
19+ )
20+import Control.Distributed.Process.Internal.CQueue
21+ ( CQueue
22+ )
23+import Control.Distributed.Process.Internal.Primitives
24+ ( die
25+ , receiveWait
26+ , forward
27+ , sendChan
28+ , match
29+ , matchAny
30+ , matchIf
31+ , handleMessage
32+ , matchUnknown
33+ )
34+import Control.Distributed.Process.Management.Internal.Types
35+ ( MxEvent(..)
36+ , Addressable(..)
37+ )
38+import Control.Distributed.Process.Management.Internal.Trace.Types
39+ ( SetTrace(..)
40+ , TraceSubject(..)
41+ , TraceFlags(..)
42+ , TraceOk(..)
43+ , defaultTraceFlags
44+ )
45+import Control.Distributed.Process.Management.Internal.Trace.Primitives
46+ ( traceOn )
47+import Control.Distributed.Process.Internal.Types
48+ ( LocalNode(..)
49+ , NCMsg(..)
50+ , ProcessId
51+ , Process
52+ , LocalProcess(..)
53+ , Identifier(..)
54+ , ProcessSignal(NamedSend)
55+ , Message
56+ , SendPort
57+ , forever'
58+ , nullProcessId
59+ , createUnencodedMessage
60+ )
61+
62+import Control.Monad.IO.Class (liftIO)
63+import Control.Monad.Reader (ask)
64+import Control.Monad.Catch
65+ ( catch
66+ , finally
67+ )
68+
69+import Data.Set (Set)
70+import qualified Data.Set as Set
71+import Data.Map (Map)
72+import qualified Data.Map as Map
73+
74+import Data.Maybe (fromMaybe)
75+import Data.Time.Clock (getCurrentTime)
76+import Data.Time.Format (formatTime)
77+import Debug.Trace (traceEventIO)
78+import Prelude
79+
80+#if ! MIN_VERSION_base(4,6,0)
81+import Prelude hiding (catch)
82+#endif
83+
84+import System.Environment (getEnv)
85+import System.IO
86+ ( Handle
87+ , IOMode(AppendMode)
88+ , BufferMode(..)
89+ , openFile
90+ , hClose
91+ , hPutStrLn
92+ , hSetBuffering
93+ )
94+#if MIN_VERSION_time(1,5,0)
95+import Data.Time.Format (defaultTimeLocale)
96+#else
97+import System.Locale (defaultTimeLocale)
98+#endif
99+import System.Mem.Weak
100+ ( Weak
101+ )
102+
103+data TracerState =
104+ TracerST
105+ {
106+ client :: !(Maybe ProcessId)
107+ , flags :: !TraceFlags
108+ , regNames :: !(Map ProcessId (Set String))
109+ }
110+
111+--------------------------------------------------------------------------------
112+-- Trace Handlers --
113+--------------------------------------------------------------------------------
114+
115+defaultTracer :: Process ()
116+defaultTracer =
117+ catch (checkEnv "DISTRIBUTED_PROCESS_TRACE_FILE" >>= logfileTracer)
118+ (\(_ :: IOError) -> defaultTracerAux)
119+
120+defaultTracerAux :: Process ()
121+defaultTracerAux =
122+ catch (checkEnv "DISTRIBUTED_PROCESS_TRACE_CONSOLE" >> systemLoggerTracer)
123+ (\(_ :: IOError) -> defaultEventLogTracer)
124+
125+-- TODO: it would be /nice/ if we had some way of checking the runtime
126+-- options to see if +RTS -v (or similar) has been given...
127+defaultEventLogTracer :: Process ()
128+defaultEventLogTracer =
129+ catch (checkEnv "DISTRIBUTED_PROCESS_TRACE_EVENTLOG" >> eventLogTracer)
130+ (\(_ :: IOError) -> nullTracer)
131+
132+checkEnv :: String -> Process String
133+checkEnv s = liftIO $ getEnv s
134+
135+-- This trace client is (intentionally) a noop - it simply provides
136+-- an intial client for the trace controller to talk to, until some
137+-- other (hopefully more useful) client is installed over the top of
138+-- it. This is the default trace client.
139+nullTracer :: Process ()
140+nullTracer =
141+ forever' $ receiveWait [ matchUnknown (return ()) ]
142+
143+systemLoggerTracer :: Process ()
144+systemLoggerTracer = do
145+ node <- processNode <$> ask
146+ let tr = sendTraceLog node
147+ forever' $ receiveWait [ matchAny (\m -> handleMessage m tr) ]
148+ where
149+ sendTraceLog :: LocalNode -> MxEvent -> Process ()
150+ sendTraceLog node ev = do
151+ now <- liftIO $ getCurrentTime
152+ msg <- return $ (formatTime defaultTimeLocale "%c" now, buildTxt ev)
153+ emptyPid <- return $ (nullProcessId (localNodeId node))
154+ traceMsg <- return $ NCMsg {
155+ ctrlMsgSender = ProcessIdentifier (emptyPid)
156+ , ctrlMsgSignal = (NamedSend "trace.logger"
157+ (createUnencodedMessage msg))
158+ }
159+ liftIO $ writeChan (localCtrlChan node) traceMsg
160+
161+ buildTxt :: MxEvent -> String
162+ buildTxt (MxLog msg) = msg
163+ buildTxt ev = show ev
164+
165+eventLogTracer :: Process ()
166+eventLogTracer =
167+ -- NB: when the GHC event log supports tracing arbitrary (ish) data, we will
168+ -- almost certainly use *that* facility independently of whether or not there
169+ -- is a tracer process installed. This is just a stop gap until then.
170+ forever' $ receiveWait [ matchAny (\m -> handleMessage m writeTrace) ]
171+ where
172+ writeTrace :: MxEvent -> Process ()
173+ writeTrace ev = liftIO $ traceEventIO (show ev)
174+
175+logfileTracer :: FilePath -> Process ()
176+logfileTracer p = do
177+ -- TODO: error handling if the handle cannot be opened
178+ h <- liftIO $ openFile p AppendMode
179+ liftIO $ hSetBuffering h LineBuffering
180+ logger h `finally` (liftIO $ hClose h)
181+ where
182+ logger :: Handle -> Process ()
183+ logger h' = forever' $ do
184+ receiveWait [
185+ matchIf (\ev -> case ev of
186+ MxTraceDisable -> True
187+ (MxTraceTakeover _) -> True
188+ _ -> False)
189+ (\_ -> (liftIO $ hClose h') >> die "trace stopped")
190+ , matchAny (\ev -> handleMessage ev (writeTrace h'))
191+ ]
192+
193+ writeTrace :: Handle -> MxEvent -> Process ()
194+ writeTrace h ev = do
195+ liftIO $ do
196+ now <- getCurrentTime
197+ hPutStrLn h $ (formatTime defaultTimeLocale "%c - " now) ++ (show ev)
198+
199+--------------------------------------------------------------------------------
200+-- Tracer Implementation --
201+--------------------------------------------------------------------------------
202+
203+traceController :: MVar ((Weak (CQueue Message))) -> Process ()
204+traceController mv = do
205+ -- See the documentation for mxAgentController for a
206+ -- commentary that explains this breach of encapsulation
207+ weakQueue <- processWeakQ <$> ask
208+ liftIO $ putMVar mv weakQueue
209+ initState <- initialState
210+ traceLoop initState { client = Nothing }
211+ where
212+ traceLoop :: TracerState -> Process ()
213+ traceLoop st = do
214+ let client' = client st
215+ -- Trace events are forwarded to the enabled trace target.
216+ -- At some point in the future, we're going to start writing these custom
217+ -- events to the ghc eventlog, at which point this design might change.
218+ st' <- receiveWait [
219+ match (\(setResp, set :: SetTrace) -> do
220+ -- We consider at most one trace client, which is a process.
221+ -- Tracking multiple clients represents too high an overhead,
222+ -- so we leave that kind of thing to our consumers (e.g., the
223+ -- high level Debug client module) to figure out.
224+ case set of
225+ (TraceEnable pid) -> do
226+ -- notify the previous tracer it has been replaced
227+ sendTraceMsg client' (createUnencodedMessage (MxTraceTakeover pid))
228+ sendOk setResp
229+ return st { client = (Just pid) }
230+ TraceDisable -> do
231+ sendTraceMsg client' (createUnencodedMessage MxTraceDisable)
232+ sendOk setResp
233+ return st { client = Nothing })
234+ , match (\(confResp, flags') ->
235+ sendOk confResp >> applyTraceFlags flags' st)
236+ , match (\chGetFlags -> sendChan chGetFlags (flags st) >> return st)
237+ , match (\chGetCurrent -> sendChan chGetCurrent (client st) >> return st)
238+ -- we dequeue incoming events even if we don't process them
239+ , matchAny (\ev ->
240+ handleMessage ev (handleTrace st ev) >>= return . fromMaybe st)
241+ ]
242+ traceLoop st'
243+
244+ sendOk :: Maybe (SendPort TraceOk) -> Process ()
245+ sendOk Nothing = return ()
246+ sendOk (Just sp) = sendChan sp TraceOk
247+
248+ initialState :: Process TracerState
249+ initialState = do
250+ flags' <- checkEnvFlags
251+ return $ TracerST { client = Nothing
252+ , flags = flags'
253+ , regNames = Map.empty
254+ }
255+
256+ checkEnvFlags :: Process TraceFlags
257+ checkEnvFlags =
258+ catch (checkEnv "DISTRIBUTED_PROCESS_TRACE_FLAGS" >>= return . parseFlags)
259+ (\(_ :: IOError) -> return defaultTraceFlags)
260+
261+ parseFlags :: String -> TraceFlags
262+ parseFlags s = parseFlags' s defaultTraceFlags
263+ where parseFlags' :: String -> TraceFlags -> TraceFlags
264+ parseFlags' [] parsedFlags = parsedFlags
265+ parseFlags' (x:xs) parsedFlags
266+ | x == 'p' = parseFlags' xs parsedFlags { traceSpawned = traceOn }
267+ | x == 'n' = parseFlags' xs parsedFlags { traceRegistered = traceOn }
268+ | x == 'u' = parseFlags' xs parsedFlags { traceUnregistered = traceOn }
269+ | x == 'd' = parseFlags' xs parsedFlags { traceDied = traceOn }
270+ | x == 's' = parseFlags' xs parsedFlags { traceSend = traceOn }
271+ | x == 'r' = parseFlags' xs parsedFlags { traceRecv = traceOn }
272+ | x == 'l' = parseFlags' xs parsedFlags { traceNodes = True }
273+ | otherwise = parseFlags' xs parsedFlags
274+
275+applyTraceFlags :: TraceFlags -> TracerState -> Process TracerState
276+applyTraceFlags flags' state = return state { flags = flags' }
277+
278+handleTrace :: TracerState -> Message -> MxEvent -> Process TracerState
279+handleTrace st msg ev@(MxRegistered p n) =
280+ let regNames' =
281+ Map.insertWith (\_ ns -> Set.insert n ns) p
282+ (Set.singleton n)
283+ (regNames st)
284+ in do
285+ traceEv ev msg (traceRegistered (flags st)) st
286+ return st { regNames = regNames' }
287+handleTrace st msg ev@(MxUnRegistered p n) =
288+ let f ns = case ns of
289+ Nothing -> Nothing
290+ Just ns' -> Just (Set.delete n ns')
291+ regNames' = Map.alter f p (regNames st)
292+ in do
293+ traceEv ev msg (traceUnregistered (flags st)) st
294+ return st { regNames = regNames' }
295+handleTrace st msg ev@(MxSpawned _) = do
296+ traceEv ev msg (traceSpawned (flags st)) st >> return st
297+handleTrace st msg ev@(MxProcessDied _ _) = do
298+ traceEv ev msg (traceDied (flags st)) st >> return st
299+handleTrace st msg ev@(MxSent _ _ _) =
300+ traceEv ev msg (traceSend (flags st)) st >> return st
301+handleTrace st msg ev@(MxReceived _ _) =
302+ traceEv ev msg (traceRecv (flags st)) st >> return st
303+handleTrace st msg ev = do
304+ case ev of
305+ (MxNodeDied _ _) ->
306+ case (traceNodes (flags st)) of
307+ True -> sendTrace st ev msg
308+ False -> return ()
309+ (MxUser _) -> sendTrace st ev msg
310+ (MxLog _) -> sendTrace st ev msg
311+ _ ->
312+ case (traceConnections (flags st)) of
313+ True -> sendTrace st ev msg
314+ False -> return ()
315+ return st
316+
317+traceEv :: MxEvent
318+ -> Message
319+ -> Maybe TraceSubject
320+ -> TracerState
321+ -> Process ()
322+traceEv _ _ Nothing _ = return ()
323+traceEv ev msg (Just TraceAll) st = sendTrace st ev msg
324+traceEv ev msg (Just (TraceProcs pids)) st = do
325+ node <- processNode <$> ask
326+ let p = case resolveToPid ev of
327+ Nothing -> (nullProcessId (localNodeId node))
328+ Just pid -> pid
329+ case (Set.member p pids) of
330+ True -> sendTrace st ev msg
331+ False -> return ()
332+traceEv ev msg (Just (TraceNames names)) st = do
333+ -- if we have recorded regnames for p, then we forward the trace iif
334+ -- there are overlapping trace targets
335+ node <- processNode <$> ask
336+ let p = case resolveToPid ev of
337+ Nothing -> (nullProcessId (localNodeId node))
338+ Just pid -> pid
339+ case (Map.lookup p (regNames st)) of
340+ Nothing -> return ()
341+ Just ns -> if (Set.null (Set.intersection ns names))
342+ then return ()
343+ else sendTrace st ev msg
344+
345+sendTrace :: TracerState -> MxEvent -> Message -> Process ()
346+sendTrace st ev msg = do
347+ let c = client st
348+ if c == (resolveToPid ev) -- we do not send the tracer events about itself...
349+ then return ()
350+ else sendTraceMsg c msg
351+
352+sendTraceMsg :: Maybe ProcessId -> Message -> Process ()
353+sendTraceMsg Nothing _ = return ()
354+sendTraceMsg (Just p) msg = (flip forward) p msg
src/Control/Distributed/Process/Management/Internal/Trace/Types.hsnew file+159
@@ -0,0 +1,159 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE ExistentialQuantification #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE TypeSynonymInstances #-}
5+{-# LANGUAGE GADTs #-}
6+{-# LANGUAGE DeriveGeneric #-}
7+
8+-- | Tracing/Debugging support - Types
9+module Control.Distributed.Process.Management.Internal.Trace.Types
10+ ( SetTrace(..)
11+ , TraceSubject(..)
12+ , TraceFlags(..)
13+ , TraceArg(..)
14+ , TraceOk(..)
15+ , traceLog
16+ , traceLogFmt
17+ , traceEvent
18+ , traceMessage
19+ , defaultTraceFlags
20+ , enableTrace
21+ , enableTraceSync
22+ , disableTrace
23+ , disableTraceSync
24+ , getTraceFlags
25+ , setTraceFlags
26+ , setTraceFlagsSync
27+ , getCurrentTraceClient
28+ ) where
29+
30+import Control.Distributed.Process.Internal.Types
31+ ( MxEventBus(..)
32+ , ProcessId
33+ , SendPort
34+ , unsafeCreateUnencodedMessage
35+ )
36+import Control.Distributed.Process.Management.Internal.Bus
37+ ( publishEvent
38+ )
39+import Control.Distributed.Process.Management.Internal.Types
40+ ( MxEvent(..)
41+ )
42+import Control.Distributed.Process.Serializable
43+import Data.Binary
44+import Data.List (intersperse)
45+import Data.Set (Set)
46+import Data.Typeable
47+import GHC.Generics
48+
49+--------------------------------------------------------------------------------
50+-- Types --
51+--------------------------------------------------------------------------------
52+
53+data SetTrace = TraceEnable !ProcessId | TraceDisable
54+ deriving (Typeable, Generic, Eq, Show)
55+instance Binary SetTrace where
56+
57+-- | Defines which processes will be traced by a given 'TraceFlag',
58+-- either by name, or @ProcessId@. Choosing @TraceAll@ is /by far/
59+-- the most efficient approach, as the tracer process therefore
60+-- avoids deciding whether or not a trace event is viable.
61+--
62+data TraceSubject =
63+ TraceAll -- enable tracing for all running processes
64+ | TraceProcs !(Set ProcessId) -- enable tracing for a set of processes
65+ | TraceNames !(Set String) -- enable tracing for a set of named/registered processes
66+ deriving (Typeable, Generic, Show)
67+instance Binary TraceSubject where
68+
69+-- | Defines /what/ will be traced. Flags that control tracing of
70+-- @Process@ events, take a 'TraceSubject' controlling which processes
71+-- should generate trace events in the target process.
72+data TraceFlags = TraceFlags {
73+ traceSpawned :: !(Maybe TraceSubject) -- filter process spawned tracing
74+ , traceDied :: !(Maybe TraceSubject) -- filter process died tracing
75+ , traceRegistered :: !(Maybe TraceSubject) -- filter process registration tracing
76+ , traceUnregistered :: !(Maybe TraceSubject) -- filter process un-registration
77+ , traceSend :: !(Maybe TraceSubject) -- filter process/message tracing by sender
78+ , traceRecv :: !(Maybe TraceSubject) -- filter process/message tracing by receiver
79+ , traceNodes :: !Bool -- enable node status trace events
80+ , traceConnections :: !Bool -- enable connection status trace events
81+ } deriving (Typeable, Generic, Show)
82+instance Binary TraceFlags where
83+
84+defaultTraceFlags :: TraceFlags
85+defaultTraceFlags =
86+ TraceFlags {
87+ traceSpawned = Nothing
88+ , traceDied = Nothing
89+ , traceRegistered = Nothing
90+ , traceUnregistered = Nothing
91+ , traceSend = Nothing
92+ , traceRecv = Nothing
93+ , traceNodes = False
94+ , traceConnections = False
95+ }
96+
97+data TraceArg =
98+ TraceStr String
99+ | forall a. (Show a) => Trace a
100+
101+-- | A generic 'ok' response from the trace coordinator.
102+data TraceOk = TraceOk
103+ deriving (Typeable, Generic)
104+instance Binary TraceOk where
105+
106+--------------------------------------------------------------------------------
107+-- Internal/Common API --
108+--------------------------------------------------------------------------------
109+
110+traceLog :: MxEventBus -> String -> IO ()
111+traceLog tr s = publishEvent tr (unsafeCreateUnencodedMessage $ MxLog s)
112+
113+traceLogFmt :: MxEventBus
114+ -> String
115+ -> [TraceArg]
116+ -> IO ()
117+traceLogFmt t d ls =
118+ traceLog t $ concat (intersperse d (map toS ls))
119+ where toS :: TraceArg -> String
120+ toS (TraceStr s) = s
121+ toS (Trace a) = show a
122+
123+traceEvent :: MxEventBus -> MxEvent -> IO ()
124+traceEvent tr ev = publishEvent tr (unsafeCreateUnencodedMessage ev)
125+
126+traceMessage :: Serializable m => MxEventBus -> m -> IO ()
127+traceMessage tr msg = traceEvent tr (MxUser (unsafeCreateUnencodedMessage msg))
128+
129+enableTrace :: MxEventBus -> ProcessId -> IO ()
130+enableTrace t p =
131+ publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)),
132+ (TraceEnable p)))
133+
134+enableTraceSync :: MxEventBus -> SendPort TraceOk -> ProcessId -> IO ()
135+enableTraceSync t s p =
136+ publishEvent t (unsafeCreateUnencodedMessage (Just s, TraceEnable p))
137+
138+disableTrace :: MxEventBus -> IO ()
139+disableTrace t =
140+ publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)),
141+ TraceDisable))
142+
143+disableTraceSync :: MxEventBus -> SendPort TraceOk -> IO ()
144+disableTraceSync t s =
145+ publishEvent t (unsafeCreateUnencodedMessage ((Just s), TraceDisable))
146+
147+setTraceFlags :: MxEventBus -> TraceFlags -> IO ()
148+setTraceFlags t f =
149+ publishEvent t (unsafeCreateUnencodedMessage ((Nothing :: Maybe (SendPort TraceOk)), f))
150+
151+setTraceFlagsSync :: MxEventBus -> SendPort TraceOk -> TraceFlags -> IO ()
152+setTraceFlagsSync t s f =
153+ publishEvent t (unsafeCreateUnencodedMessage ((Just s), f))
154+
155+getTraceFlags :: MxEventBus -> SendPort TraceFlags -> IO ()
156+getTraceFlags t s = publishEvent t (unsafeCreateUnencodedMessage s)
157+
158+getCurrentTraceClient :: MxEventBus -> SendPort (Maybe ProcessId) -> IO ()
159+getCurrentTraceClient t s = publishEvent t (unsafeCreateUnencodedMessage s)
src/Control/Distributed/Process/Management/Internal/Types.hsnew file+141
@@ -0,0 +1,141 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE ExistentialQuantification #-}
3+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE DeriveGeneric #-}
5+module Control.Distributed.Process.Management.Internal.Types
6+ ( MxAgentId(..)
7+ , MxAgentState(..)
8+ , MxAgent(..)
9+ , MxAction(..)
10+ , ChannelSelector(..)
11+ , Fork
12+ , MxSink
13+ , MxEvent(..)
14+ , Addressable(..)
15+ ) where
16+
17+import Control.Applicative (Applicative)
18+import Control.Concurrent.STM
19+ ( TChan
20+ )
21+import Control.Distributed.Process.Internal.Types
22+ ( Process
23+ , ProcessId
24+ , SendPortId
25+ , Message
26+ , DiedReason
27+ , NodeId
28+ )
29+import Control.Monad.IO.Class (MonadIO)
30+import qualified Control.Monad.State as ST
31+ ( MonadState
32+ , StateT
33+ )
34+import Control.Monad.Fix (MonadFix)
35+import Data.Binary
36+import Data.Typeable (Typeable)
37+import GHC.Generics
38+import Network.Transport
39+ ( ConnectionId
40+ , EndPointAddress
41+ )
42+
43+-- | This is the /default/ management event, fired for various internal
44+-- events around the NT connection and Process lifecycle. All published
45+-- events that conform to this type, are eligible for tracing - i.e.,
46+-- they will be delivered to the trace controller.
47+--
48+data MxEvent =
49+ MxSpawned ProcessId
50+ -- ^ fired whenever a local process is spawned
51+ | MxRegistered ProcessId String
52+ -- ^ fired whenever a process/name is registered (locally)
53+ | MxUnRegistered ProcessId String
54+ -- ^ fired whenever a process/name is unregistered (locally)
55+ | MxProcessDied ProcessId DiedReason
56+ -- ^ fired whenever a process dies
57+ | MxNodeDied NodeId DiedReason
58+ -- ^ fired whenever a node /dies/ (i.e., the connection is broken/disconnected)
59+ | MxSent ProcessId ProcessId Message
60+ -- ^ fired whenever a message is sent from a local process
61+ | MxSentToName String ProcessId Message
62+ -- ^ fired whenever a named send occurs
63+ | MxSentToPort ProcessId SendPortId Message
64+ -- ^ fired whenever a sendChan occurs
65+ | MxReceived ProcessId Message
66+ -- ^ fired whenever a message is received by a local process
67+ | MxReceivedPort SendPortId Message
68+ -- ^ fired whenever a message is received via a typed channel
69+ | MxConnected ConnectionId EndPointAddress
70+ -- ^ fired when a network-transport connection is first established
71+ | MxDisconnected ConnectionId EndPointAddress
72+ -- ^ fired when a network-transport connection is broken/disconnected
73+ | MxUser Message
74+ -- ^ a user defined trace event
75+ | MxLog String
76+ -- ^ a /logging/ event - used for debugging purposes only
77+ | MxTraceTakeover ProcessId
78+ -- ^ notifies a trace listener that all subsequent traces will be sent to /pid/
79+ | MxTraceDisable
80+ -- ^ notifies a trace listener that it has been disabled/removed
81+ deriving (Typeable, Generic, Show)
82+
83+instance Binary MxEvent where
84+
85+-- | The class of things that we might be able to resolve to
86+-- a @ProcessId@ (or not).
87+class Addressable a where
88+ resolveToPid :: a -> Maybe ProcessId
89+
90+instance Addressable MxEvent where
91+ resolveToPid (MxSpawned p) = Just p
92+ resolveToPid (MxProcessDied p _) = Just p
93+ resolveToPid (MxSent _ p _) = Just p
94+ resolveToPid (MxReceived p _) = Just p
95+ resolveToPid _ = Nothing
96+
97+-- | Gross though it is, this synonym represents a function
98+-- used to forking new processes, which has to be passed as a HOF
99+-- when calling mxAgentController, since there's no other way to
100+-- avoid a circular dependency with Node.hs
101+type Fork = (Process () -> IO ProcessId)
102+
103+-- | A newtype wrapper for an agent id (which is a string).
104+newtype MxAgentId = MxAgentId { agentId :: String }
105+ deriving (Typeable, Binary, Eq, Ord)
106+
107+data MxAgentState s = MxAgentState
108+ {
109+ mxAgentId :: !MxAgentId
110+ , mxBus :: !(TChan Message)
111+ , mxLocalState :: !s
112+ }
113+
114+-- | Monad for management agents.
115+--
116+newtype MxAgent s a =
117+ MxAgent
118+ {
119+ unAgent :: ST.StateT (MxAgentState s) Process a
120+ } deriving ( Functor
121+ , Monad
122+ , MonadIO
123+ , MonadFix
124+ , ST.MonadState (MxAgentState s)
125+ , Typeable
126+ , Applicative
127+ )
128+
129+data ChannelSelector = InputChan | Mailbox
130+
131+-- | Represents the actions a management agent can take
132+-- when evaluating an /event sink/.
133+--
134+data MxAction =
135+ MxAgentDeactivate !String
136+ | MxAgentPrioritise !ChannelSelector
137+ | MxAgentReady
138+ | MxAgentSkip
139+
140+-- | Type of a management agent's event sink.
141+type MxSink s = Message -> MxAgent s (Maybe MxAction)
src/Control/Distributed/Process/Node.hs+486−263
@@ -1,7 +1,16 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveDataTypeable #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+{-# LANGUAGE RankNTypes #-}
5+{-# LANGUAGE BangPatterns #-}
6+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7+{-# LANGUAGE RecordWildCards #-}
8+{-# LANGUAGE MagicHash #-}
9+
110-- | Local nodes
211--
312module Control.Distributed.Process.Node
4− ( LocalNode(localEndPoint)
13+ ( LocalNode
514 , newLocalNode
615 , closeLocalNode
716 , forkProcess
@@ -12,10 +21,6 @@ module Control.Distributed.Process.Node
1221
1322-- TODO: Calls to 'sendBinary' and co (by the NC) may stall the node controller.
1423
15−#if ! MIN_VERSION_base(4,6,0)
16−import Prelude hiding (catch)
17−#endif
18−
1924import System.IO (fixIO, hPutStrLn, stderr)
2025import System.Mem.Weak (Weak, deRefWeak)
2126import qualified Data.ByteString.Lazy as BSL (fromChunks)
@@ -23,51 +28,73 @@ import Data.Binary (decode)
2328import Data.Map (Map)
2429import qualified Data.Map as Map
2530 ( empty
31+ , delete
2632 , toList
2733 , fromList
28− , filter
34+ , insert
35+ , lookup
36+ , partition
2937 , partitionWithKey
3038 , elems
39+ , size
3140 , filterWithKey
3241 , foldlWithKey
3342 )
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
3449import Data.Set (Set)
3550import qualified Data.Set as Set
3651 ( empty
3752 , insert
3853 , delete
54+ , map
3955 , member
4056 , toList
57+ , union
4158 )
4259import Data.Foldable (forM_)
43−import Data.Maybe (isJust, isNothing, catMaybes)
60+import Data.Maybe (isJust, fromJust, isNothing, catMaybes)
4461import Data.Typeable (Typeable)
4562import Control.Category ((>>>))
46−import Control.Applicative ((<$>))
47−import Control.Monad (void, when)
63+import Control.Applicative
64+import Control.Monad (void, when, join)
4865import Control.Monad.IO.Class (MonadIO, liftIO)
4966import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets)
5067import qualified Control.Monad.State.Strict as StateT (get, put)
5168import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
5269import Control.Exception
5370 ( throwIO
54− , AsyncException(ThreadKilled)
5571 , SomeException
5672 , Exception
5773 , throwTo
74+ , uninterruptibleMask_
75+ , getMaskingState
76+ , MaskingState(..)
5877 )
59−import qualified Control.Exception as Exception (catch)
60−import Control.Concurrent (forkIO)
78+import qualified Control.Exception as Exception
79+ ( Handler(..)
80+ , catch
81+ , catches
82+ , finally
83+ )
84+import Control.Concurrent (forkIO, killThread)
85+import Control.Distributed.Process.Internal.BiMultiMap (BiMultiMap)
86+import qualified Control.Distributed.Process.Internal.BiMultiMap as BiMultiMap
6187import Control.Distributed.Process.Internal.StrictMVar
6288 ( newMVar
6389 , withMVar
90+ , modifyMVarMasked
6491 , modifyMVar
65− , modifyMVar_
6692 , newEmptyMVar
6793 , putMVar
6894 , takeMVar
6995 )
7096import Control.Concurrent.Chan (newChan, writeChan, readChan)
97+import qualified Control.Concurrent.MVar as MVar (newEmptyMVar, takeMVar)
7198import Control.Concurrent.STM
7299 ( atomically
73100 )
@@ -76,6 +103,7 @@ import Control.Distributed.Process.Internal.CQueue
76103 , enqueue
77104 , newCQueue
78105 , mkWeakCQueue
106+ , queueSize
79107 )
80108import qualified Network.Transport as NT
81109 ( Transport
@@ -87,8 +115,8 @@ import qualified Network.Transport as NT
87115 , TransportError(..)
88116 , address
89117 , closeEndPoint
90− , ConnectionId
91118 , Connection
119+ , ConnectionId
92120 , close
93121 , EndPointAddress
94122 , Reliability(ReliableOrdered)
@@ -105,8 +133,11 @@ import Control.Distributed.Process.Internal.Types
105133 , LocalProcessId(..)
106134 , ProcessId(..)
107135 , LocalNode(..)
108− , Tracer(InactiveTracer)
136+ , MxEventBus(..)
109137 , LocalNodeState(..)
138+ , ValidLocalNodeState(..)
139+ , withValidLocalState
140+ , modifyValidLocalState
110141 , LocalProcess(..)
111142 , LocalProcessState(..)
112143 , Process(..)
@@ -116,9 +147,11 @@ import Control.Distributed.Process.Internal.Types
116147 , localPidCounter
117148 , localPidUnique
118149 , localProcessWithId
150+ , localProcesses
119151 , localConnections
120152 , forever'
121153 , MonitorRef(..)
154+ , NodeClosedException(..)
122155 , ProcessMonitorNotification(..)
123156 , NodeMonitorNotification(..)
124157 , PortMonitorNotification(..)
@@ -138,54 +171,81 @@ import Control.Distributed.Process.Internal.Types
138171 , nodeOf
139172 , ProcessInfo(..)
140173 , ProcessInfoNone(..)
174+ , NodeStats(..)
141175 , SendPortId(..)
142176 , typedChannelWithId
143177 , RegisterReply(..)
144178 , WhereIsReply(..)
145− , messageToPayload
146179 , payloadToMessage
147180 , createUnencodedMessage
181+ , unsafeCreateUnencodedMessage
148182 , runLocalProcess
149183 , firstNonReservedProcessId
150− , ImplicitReconnect(WithImplicitReconnect, NoImplicitReconnect)
184+ , ImplicitReconnect(WithImplicitReconnect)
185+ )
186+import Control.Distributed.Process.Management.Internal.Agent
187+ ( mxAgentController
188+ )
189+import Control.Distributed.Process.Management.Internal.Types
190+ ( MxEvent(..)
191+ )
192+import qualified Control.Distributed.Process.Management.Internal.Trace.Remote as Trace
193+ ( remoteTable
151194 )
152−import Control.Distributed.Process.Internal.Trace
195+import Control.Distributed.Process.Management.Internal.Trace.Tracer
196+ ( defaultTracer
197+ )
198+import Control.Distributed.Process.Management.Internal.Trace.Types
153199 ( TraceArg(..)
154− , traceFormat
155− , startTracing
156− , stopTracer
200+ , traceEvent
201+ , traceLogFmt
202+ , enableTrace
157203 )
158204import Control.Distributed.Process.Serializable (Serializable)
159205import Control.Distributed.Process.Internal.Messaging
160206 ( sendBinary
161− , sendMessage
162− , sendPayload
163207 , closeImplicitReconnections
164208 , impliesDeathOf
165209 )
166210import Control.Distributed.Process.Internal.Primitives
167211 ( register
168− , finally
169212 , receiveWait
170213 , match
171214 , sendChan
215+ , unwrapMessage
216+ , monitor
217+ , unmonitorAsync
218+ , getSelfNode
219+ , SayMessage(..)
172220 )
173−import Control.Distributed.Process.Internal.Types (SendPort)
221+import Control.Distributed.Process.Internal.Types (SendPort, Tracer(..))
174222import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn (remoteTable)
175223import Control.Distributed.Process.Internal.WeakTQueue (TQueue, writeTQueue)
176224import qualified Control.Distributed.Process.Internal.StrictContainerAccessors as DAC
177225 ( mapMaybe
178226 , mapDefault
179227 )
228+import Control.Monad.Catch (try)
229+import GHC.IO (IO(..), unsafeUnmask)
230+import GHC.Base ( maskAsyncExceptions# )
180231
181232import 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
182242
183243--------------------------------------------------------------------------------
184244-- Initialization --
185245--------------------------------------------------------------------------------
186246
187247initRemoteTable :: RemoteTable
188−initRemoteTable = BuiltIn.remoteTable Static.initRemoteTable
248+initRemoteTable = Trace.remoteTable $ BuiltIn.remoteTable Static.initRemoteTable
189249
190250-- | Initialize a new local node.
191251newLocalNode :: NT.Transport -> RemoteTable -> IO LocalNode
@@ -202,7 +262,7 @@ newLocalNode transport rtable = do
202262createBareLocalNode :: NT.EndPoint -> RemoteTable -> IO LocalNode
203263createBareLocalNode endPoint rtable = do
204264 unq <- randomIO
205− state <- newMVar LocalNodeState
265+ state <- newMVar $ LocalNodeValid $ ValidLocalNodeState
206266 { _localProcesses = Map.empty
207267 , _localPidCounter = firstNonReservedProcessId
208268 , _localPidUnique = unq
@@ -213,22 +273,19 @@ createBareLocalNode endPoint rtable = do
213273 , localEndPoint = endPoint
214274 , localState = state
215275 , localCtrlChan = ctrlChan
216− , localTracer = InactiveTracer
276+ , localEventBus = MxEventBusInitialising
217277 , remoteTable = rtable
218278 }
219− tracedNode <- startTracing node
279+ tracedNode <- startMxAgent node
220280
221− ncGo <- newEmptyMVar
222− ncTid <- forkIO $ do
223− tid <- takeMVar ncGo
224− (runNodeController tracedNode
225− `Exception.catch` \(e :: SomeException) -> throwTo tid e)
281+ -- Once the NC terminates, the endpoint isn't much use,
282+ void $ forkIO $ Exception.finally (runNodeController tracedNode)
283+ (NT.closeEndPoint (localEndPoint node))
226284
227− evTid <- forkIO $ do
228− (handleIncomingMessages tracedNode >> (stopNC node))
229− `Exception.catch` \(e :: SomeException) -> throwTo ncTid e
285+ -- whilst a closed/failing endpoint will terminate the NC
286+ void $ forkIO $ Exception.finally (handleIncomingMessages tracedNode)
287+ (stopNC node)
230288
231− putMVar ncGo evTid
232289 return tracedNode
233290 where
234291 stopNC node =
@@ -237,48 +294,100 @@ createBareLocalNode endPoint rtable = do
237294 , ctrlMsgSignal = SigShutdown
238295 }
239296
297+startMxAgent :: LocalNode -> IO LocalNode
298+startMxAgent node = do
299+ -- see note [tracer/forkProcess races]
300+ let fork = forkProcess node
301+ mv <- MVar.newEmptyMVar
302+ pid <- fork $ mxAgentController fork mv
303+ (tracer', wqRef, mxNew') <- MVar.takeMVar mv
304+ return node { localEventBus = (MxEventBus pid tracer' wqRef mxNew') }
305+
306+startDefaultTracer :: LocalNode -> IO ()
307+startDefaultTracer node' = do
308+ let t = localEventBus node'
309+ case t of
310+ MxEventBus _ (Tracer pid _) _ _ -> do
311+ runProcess node' $ register "trace.controller" pid
312+ pid' <- forkProcess node' defaultTracer
313+ enableTrace (localEventBus node') pid'
314+ runProcess node' $ register "tracer.initial" pid'
315+ _ -> return ()
316+
317+-- TODO: we need a better mechanism for defining and registering services
318+
240319-- | Start and register the service processes on a node
241−-- (for now, this is only the logger)
242320startServiceProcesses :: LocalNode -> IO ()
243321startServiceProcesses node = do
322+ -- tracing /spawns/ relies on the tracer being enabled, but we start
323+ -- the default tracer first, even though it might @nsend@ to the logger
324+ -- before /that/ process has started - this is a totally harmless race
325+ -- however, so we deliberably ignore it
326+ startDefaultTracer node
244327 logger <- forkProcess node loop
245− runProcess node $ register "logger" logger
328+ runProcess node $ do
329+ register "logger" logger
330+ -- The trace.logger is used for tracing to the console to avoid feedback
331+ -- loops during tracing if the user reregisters the "logger" with a custom
332+ -- process which uses 'send' or other primitives which are traced.
333+ register "trace.logger" logger
246334 where
247− loop = do
248− receiveWait
249− [ match $ \((time, pid, string) ::(String, ProcessId, String)) -> do
250− liftIO . hPutStrLn stderr $ time ++ " " ++ show pid ++ ": " ++ string
251− loop
252− , match $ \((time, string) :: (String, String)) -> do
253− -- this is a 'trace' message from the local node tracer
254− liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
255− loop
256− , match $ \(ch :: SendPort ()) -> -- a shutdown request
257− sendChan ch ()
258− ]
259−
260−-- | Force-close a local node
261−--
262−-- TODO: for now we just close the associated endpoint
335+ loop = do
336+ receiveWait
337+ [ match $ \(SayMessage time pid string) -> do
338+ let time' = formatTime defaultTimeLocale "%c" time
339+ liftIO . hPutStrLn stderr $ time' ++ " " ++ show pid ++ ": " ++ string
340+ loop
341+ , match $ \((time, string) :: (String, String)) -> do
342+ -- this is a 'trace' message from the local node tracer
343+ liftIO . hPutStrLn stderr $ time ++ " [trace] " ++ string
344+ loop
345+ , match $ \(ch :: SendPort ()) -> -- a shutdown request
346+ sendChan ch ()
347+ ]
348+
349+-- | Force-close a local node, killing all processes on that node.
263350closeLocalNode :: LocalNode -> IO ()
264−closeLocalNode node =
351+closeLocalNode node = do
352+ -- Kill processes after refilling the mvar. Otherwise, there is potential for
353+ -- deadlock as a dying process tries to get the mvar while masking exceptions
354+ -- uninterruptibly.
355+ join $ modifyMVar (localState node) $ \st -> case st of
356+ LocalNodeValid vst -> do
357+ return ( LocalNodeClosed
358+ , forM_ (vst ^. localProcesses) $ \lproc ->
359+ -- Semantics of 'throwTo' guarantee that target thread will get
360+ -- delivered an exception. Therefore, target thread will be
361+ -- killed eventually and that's as good as we can do. No need
362+ -- to wait for thread to actually finish dying.
363+ killThread (processThread lproc)
364+ )
365+ LocalNodeClosed -> return (LocalNodeClosed, return ())
366+ -- This call will have the effect of shutting down the NC as well (see
367+ -- 'createBareLocalNode').
265368 NT.closeEndPoint (localEndPoint node)
266369
267370-- | Run a process on a local node and wait for it to finish
268371runProcess :: LocalNode -> Process () -> IO ()
269372runProcess node proc = do
270373 done <- newEmptyMVar
271− void $ forkProcess node (proc `finally` liftIO (putMVar done ()))
272− takeMVar done
374+ -- TODO; When forkProcess inherits the masking state, protect the forked
375+ -- thread against async exceptions that could occur before 'try' is evaluated.
376+ void $ forkProcess node $ try proc >>= liftIO . putMVar done
377+ takeMVar done >>= either (throwIO :: SomeException -> IO a) return
273378
274379-- | Spawn a new process on a local node
275380forkProcess :: LocalNode -> Process () -> IO ProcessId
276−forkProcess node proc = modifyMVar (localState node) startProcess
381+forkProcess node proc = do
382+ ms <- getMaskingState
383+ modifyMVarMasked (localState node) (startProcess ms)
277384 where
278− startProcess :: LocalNodeState -> IO (LocalNodeState, ProcessId)
279− startProcess st = do
280− let lpid = LocalProcessId { lpidCounter = st ^. localPidCounter
281− , lpidUnique = st ^. localPidUnique
385+ startProcess :: MaskingState
386+ -> LocalNodeState
387+ -> IO (LocalNodeState, ProcessId)
388+ startProcess ms (LocalNodeValid vst) = do
389+ let lpid = LocalProcessId { lpidCounter = vst ^. localPidCounter
390+ , lpidUnique = vst ^. localPidUnique
282391 }
283392 let pid = ProcessId { processNodeId = localNodeId node
284393 , processLocalId = lpid
@@ -298,43 +407,81 @@ forkProcess node proc = modifyMVar (localState node) startProcess
298407 , processThread = tid
299408 , processNode = node
300409 }
301− tid' <- forkIO $ do
302− reason <- Exception.catch
303− (runLocalProcess lproc proc >> return DiedNormal)
304− (return . DiedException . (show :: SomeException -> String))
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
417+ reason <- Exception.catches
418+ (unmask $ runLocalProcess lproc proc >> return DiedNormal)
419+ [ (Exception.Handler (\ex@(ProcessExitException from msg) -> do
420+ mMsg <- unwrapMessage msg :: IO (Maybe String)
421+ case mMsg of
422+ Nothing -> return $ DiedException $ show ex
423+ Just m -> return $ DiedException ("exit-from=" ++ (show from) ++ ",reason=" ++ m)))
424+ , (Exception.Handler
425+ (return . DiedException . (show :: SomeException -> String)))]
305426
306427 -- [Unified: Table 4, rules termination and exiting]
307− modifyMVar_ (localState node) (cleanupProcess pid)
428+ mconns <- modifyValidLocalState node (cleanupProcess pid)
429+ -- XXX: Revisit after agreeing on the bigger picture for the semantics
430+ -- of transport operations.
431+ -- https://github.com/haskell-distributed/distributed-process/issues/204
432+ forM_ mconns $ forkIO . mapM_ NT.close
433+
308434 writeChan (localCtrlChan node) NCMsg
309435 { ctrlMsgSender = ProcessIdentifier pid
310436 , ctrlMsgSignal = Died (ProcessIdentifier pid) reason
311437 }
312438 return (tid', lproc)
313439
440+ -- see note [tracer/forkProcess races]
441+ trace node (MxSpawned pid)
442+
314443 if lpidCounter lpid == maxBound
315444 then do
445+ -- TODO: this doesn't look right at all - how do we know
446+ -- that newUnique represents a process id that is available!?
316447 newUnique <- randomIO
317− return ( (localProcessWithId lpid ^= Just lproc)
448+ return ( LocalNodeValid
449+ $ (localProcessWithId lpid ^= Just lproc)
318450 . (localPidCounter ^= firstNonReservedProcessId)
319451 . (localPidUnique ^= newUnique)
320− $ st
452+ $ vst
321453 , pid
322454 )
323455 else
324− return ( (localProcessWithId lpid ^= Just lproc)
456+ return ( LocalNodeValid
457+ $ (localProcessWithId lpid ^= Just lproc)
325458 . (localPidCounter ^: (+ 1))
326− $ st
459+ $ vst
327460 , pid
328461 )
462+ startProcess _ LocalNodeClosed =
463+ throwIO $ NodeClosedException $ localNodeId node
329464
330− cleanupProcess :: ProcessId -> LocalNodeState -> IO LocalNodeState
331− cleanupProcess pid st = do
465+ cleanupProcess :: ProcessId
466+ -> ValidLocalNodeState
467+ -> IO (ValidLocalNodeState, [NT.Connection])
468+ cleanupProcess pid vst = do
332469 let pid' = ProcessIdentifier pid
333− let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (st ^. localConnections)
334− mapM_ (NT.close . fst) (Map.elems affected)
335− return $ (localProcessWithId (processLocalId pid) ^= Nothing)
336− . (localConnections ^= unaffected)
337− $ st
470+ let (affected, unaffected) = Map.partitionWithKey (\(fr, _to) !_v -> impliesDeathOf pid' fr) (vst ^. localConnections)
471+ return ( (localProcessWithId (processLocalId pid) ^= Nothing)
472+ . (localConnections ^= unaffected)
473+ $ vst
474+ , map fst $ Map.elems affected
475+ )
476+
477+-- note [tracer/forkProcess races]
478+--
479+-- Our startTracing function uses forkProcess to start the trace controller
480+-- process, and of course forkProcess attempts to call traceEvent once the
481+-- process has started. This is harmless, as the localEventBus is not updated
482+-- until /after/ the initial forkProcess completes, so the first call to
483+-- traceEvent behaves as if tracing were disabled (i.e., it is ignored).
484+--
338485
339486--------------------------------------------------------------------------------
340487-- Handle incoming messages --
@@ -344,8 +491,8 @@ type IncomingConnection = (NT.EndPointAddress, IncomingTarget)
344491
345492data IncomingTarget =
346493 Uninit
347− | ToProc (Weak (CQueue Message))
348− | ToChan TypedChannel
494+ | ToProc ProcessId (Weak (CQueue Message))
495+ | ToChan SendPortId TypedChannel
349496 | ToNode
350497
351498data ConnectionState = ConnectionState {
@@ -372,6 +519,7 @@ incomingFrom addr = aux >>> DAC.mapDefault Set.empty addr
372519
373520handleIncomingMessages :: LocalNode -> IO ()
374521handleIncomingMessages node = go initConnectionState
522+ `Exception.catch` \(NodeClosedException _) -> return ()
375523 where
376524 go :: ConnectionState -> IO ()
377525 go !st = do
@@ -379,28 +527,37 @@ handleIncomingMessages node = go initConnectionState
379527 case event of
380528 NT.ConnectionOpened cid rel theirAddr ->
381529 if rel == NT.ReliableOrdered
382− then go ( (incomingAt cid ^= Just (theirAddr, Uninit))
530+ then
531+ trace node (MxConnected cid theirAddr)
532+ >> go (
533+ (incomingAt cid ^= Just (theirAddr, Uninit))
383534 . (incomingFrom theirAddr ^: Set.insert cid)
384535 $ st
385536 )
386− else invalidRequest cid st
537+ else invalidRequest cid st $
538+ "attempt to connect with unsupported reliability " ++ show rel
387539 NT.Received cid payload ->
388540 case st ^. incomingAt cid of
389− Just (_, ToProc weakQueue) -> do
541+ Just (_, ToProc pid weakQueue) -> do
390542 mQueue <- deRefWeak weakQueue
391543 forM_ mQueue $ \queue -> do
392544 -- TODO: if we find that the queue is Nothing, should we remove
393545 -- it from the NC state? (and same for channels, below)
394546 let msg = payloadToMessage payload
395547 enqueue queue msg -- 'enqueue' is strict
548+ trace node (MxReceived pid msg)
396549 go st
397− Just (_, ToChan (TypedChannel chan')) -> do
550+ Just (_, ToChan chId (TypedChannel chan')) -> do
398551 mChan <- deRefWeak chan'
399552 -- If mChan is Nothing, the process has given up the read end of
400553 -- the channel and we simply ignore the incoming message
401− forM_ mChan $ \chan -> atomically $
402− -- We make sure the message is fully decoded when it is enqueued
403− writeTQueue chan $! decode (BSL.fromChunks payload)
554+ forM_ mChan $ \chan -> do
555+ msg' <- atomically $ do
556+ msg <- return $! decode (BSL.fromChunks payload)
557+ -- We make sure the message is fully decoded when it is enqueued
558+ writeTQueue chan msg
559+ return msg
560+ trace node $ MxReceivedPort chId $ unsafeCreateUnencodedMessage msg'
404561 go st
405562 Just (_, ToNode) -> do
406563 let ctrlMsg = decode . BSL.fromChunks $ payload
@@ -410,37 +567,49 @@ handleIncomingMessages node = go initConnectionState
410567 case decode (BSL.fromChunks payload) of
411568 ProcessIdentifier pid -> do
412569 let lpid = processLocalId pid
413− mProc <- withMVar state $ return . (^. localProcessWithId lpid)
570+ mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
414571 case mProc of
415572 Just proc ->
416− go (incomingAt cid ^= Just (src, ToProc (processWeakQ proc)) $ st)
573+ go (incomingAt cid ^= Just (src, ToProc pid (processWeakQ proc)) $ st)
417574 Nothing ->
418− invalidRequest cid st
575+ -- incoming attempt to connect to unknown process - might
576+ -- be dead already
577+ go (incomingAt cid ^= Nothing $ st)
419578 SendPortIdentifier chId -> do
420579 let lcid = sendPortLocalId chId
421580 lpid = processLocalId (sendPortProcessId chId)
422− mProc <- withMVar state $ return . (^. localProcessWithId lpid)
581+ mProc <- withValidLocalState node $ return . (^. localProcessWithId lpid)
423582 case mProc of
424583 Just proc -> do
425584 mChannel <- withMVar (processState proc) $ return . (^. typedChannelWithId lcid)
426585 case mChannel of
427586 Just channel ->
428− go (incomingAt cid ^= Just (src, ToChan channel) $ st)
587+ go (incomingAt cid ^= Just (src, ToChan chId channel) $ st)
429588 Nothing ->
430− invalidRequest cid st
589+ invalidRequest cid st $
590+ "incoming attempt to connect to unknown channel of"
591+ ++ " process " ++ show (sendPortProcessId chId)
431592 Nothing ->
432− invalidRequest cid st
593+ -- incoming attempt to connect to channel of unknown
594+ -- process - might be dead already
595+ go (incomingAt cid ^= Nothing $ st)
433596 NodeIdentifier nid ->
434597 if nid == localNodeId node
435598 then go (incomingAt cid ^= Just (src, ToNode) $ st)
436− 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
437604 Nothing ->
438605 invalidRequest cid st
606+ "message received from an unknown connection"
439607 NT.ConnectionClosed cid ->
440608 case st ^. incomingAt cid of
441609 Nothing ->
442− invalidRequest cid st
443− Just (src, _) ->
610+ invalidRequest cid st "closed unknown connection"
611+ Just (src, _) -> do
612+ trace node (MxDisconnected cid src)
444613 go ( (incomingAt cid ^= Nothing)
445614 . (incomingFrom src ^: Set.delete cid)
446615 $ st
@@ -459,32 +628,30 @@ handleIncomingMessages node = go initConnectionState
459628 $ st
460629 )
461630 NT.ErrorEvent (NT.TransportError NT.EventEndPointFailed str) ->
462− fatal $ "Cloud Haskell fatal error: end point failed: " ++ str
631+ fail $ "Cloud Haskell fatal error: end point failed: " ++ str
463632 NT.ErrorEvent (NT.TransportError NT.EventTransportFailed str) ->
464− fatal $ "Cloud Haskell fatal error: transport failed: " ++ str
633+ fail $ "Cloud Haskell fatal error: transport failed: " ++ str
465634 NT.EndPointClosed ->
466− stopTracer (localTracer node) >> return ()
635+ return ()
467636 NT.ReceivedMulticast _ _ ->
468637 -- If we received a multicast message, something went horribly wrong
469638 -- and we just give up
470− fatal "Cloud Haskell fatal error: received unexpected multicast"
471−
472− fatal :: String -> IO ()
473− fatal msg = stopTracer (localTracer node) >> fail msg
639+ fail "Cloud Haskell fatal error: received unexpected multicast"
474640
475− invalidRequest :: NT.ConnectionId -> ConnectionState -> IO ()
476− invalidRequest cid st = do
641+ invalidRequest :: NT.ConnectionId -> ConnectionState -> String -> IO ()
642+ invalidRequest cid st msg = do
477643 -- TODO: We should treat this as a fatal error on the part of the remote
478644 -- node. That is, we should report the remote node as having died, and we
479645 -- should close incoming connections (this requires a Transport layer
480646 -- extension).
481− traceEventFmtIO node "" [(TraceStr "[network] invalid request: "),
482− (Trace cid)]
647+ traceEventFmtIO node "" [ TraceStr $ " [network] invalid request"
648+ ++ " (" ++ msg ++ "): "
649+ , (Trace cid)
650+ ]
483651 go ( incomingAt cid ^= Nothing
484652 $ st
485653 )
486654
487− state = localState node
488655 endpoint = localEndPoint node
489656 ctrlChan = localCtrlChan node
490657
@@ -493,8 +660,9 @@ handleIncomingMessages node = go initConnectionState
493660--------------------------------------------------------------------------------
494661
495662runNodeController :: LocalNode -> IO ()
496−runNodeController =
497− runReaderT (evalStateT (unNC nodeController) initNCState)
663+runNodeController node =
664+ runReaderT (evalStateT (unNC nodeController) initNCState) node
665+ `Exception.catch` \(NodeClosedException _) -> return ()
498666
499667--------------------------------------------------------------------------------
500668-- Internal data types --
@@ -502,20 +670,26 @@ runNodeController =
502670
503671data NCState = NCState
504672 { -- Mapping from remote processes to linked local processes
505− _links :: !(Map Identifier (Set ProcessId))
673+ _links :: !(BiMultiMap Identifier ProcessId ())
506674 -- Mapping from remote processes to monitoring local processes
507− , _monitors :: !(Map Identifier (Set (ProcessId, MonitorRef)))
675+ , _monitors :: !(BiMultiMap Identifier ProcessId MonitorRef)
508676 -- Process registry: names and where they live, mapped to the PIDs
509677 , _registeredHere :: !(Map String ProcessId)
510678 , _registeredOnNodes :: !(Map ProcessId [(NodeId,Int)])
511679 }
512680
513681newtype NC a = NC { unNC :: StateT NCState (ReaderT LocalNode IO) a }
514− deriving (Functor, Monad, MonadIO, MonadState NCState, MonadReader LocalNode)
682+ deriving ( Applicative
683+ , Functor
684+ , Monad
685+ , MonadIO
686+ , MonadState NCState
687+ , MonadReader LocalNode
688+ )
515689
516690initNCState :: NCState
517−initNCState = NCState { _links = Map.empty
518− , _monitors = Map.empty
691+initNCState = NCState { _links = BiMultiMap.empty
692+ , _monitors = BiMultiMap.empty
519693 , _registeredHere = Map.empty
520694 , _registeredOnNodes = Map.empty
521695 }
@@ -531,34 +705,60 @@ instance Show ProcessKillException where
531705 show (ProcessKillException pid reason) =
532706 "killed-by=" ++ show pid ++ ",reason=" ++ reason
533707
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+
534735--------------------------------------------------------------------------------
535736-- Tracing/Debugging --
536737--------------------------------------------------------------------------------
537738
538−-- [Issue #104]
739+-- [Issue #104 / DP-13]
539740
540741traceNotifyDied :: LocalNode -> Identifier -> DiedReason -> NC ()
541742traceNotifyDied node ident reason =
542− case reason of
543− DiedNormal -> return ()
544− _ -> traceNcEventFmt node " "
545− [(TraceStr "[node-controller]"),
546− (Trace ident),
547− (Trace reason)]
548−
549−traceNcEventFmt :: LocalNode -> String -> [TraceArg] -> NC ()
550−traceNcEventFmt node fmt args =
551− liftIO $ traceEventFmtIO node fmt args
743+ -- TODO: sendPortDied notifications
744+ liftIO $ withLocalTracer node $ \t ->
745+ case ident of
746+ (NodeIdentifier nid) -> traceEvent t (MxNodeDied nid reason)
747+ (ProcessIdentifier pid) -> traceEvent t (MxProcessDied pid reason)
748+ _ -> return ()
552749
553750traceEventFmtIO :: LocalNode
554751 -> String
555752 -> [TraceArg]
556753 -> IO ()
557754traceEventFmtIO node fmt args =
558− withLocalTracer node $ \t -> traceFormat t fmt args
755+ withLocalTracer node $ \t -> traceLogFmt t fmt args
559756
560−withLocalTracer :: LocalNode -> (Tracer -> IO ()) -> IO ()
561−withLocalTracer node act = act (localTracer node)
757+trace :: LocalNode -> MxEvent -> IO ()
758+trace node ev = withLocalTracer node $ \t -> traceEvent t ev
759+
760+withLocalTracer :: LocalNode -> (MxEventBus -> IO ()) -> IO ()
761+withLocalTracer node act = act (localEventBus node)
562762
563763--------------------------------------------------------------------------------
564764-- Core functionality --
@@ -574,11 +774,7 @@ nodeController = do
574774 -- [Unified: Table 7, rule nc_forward]
575775 case destNid (ctrlMsgSignal msg) of
576776 Just nid' | nid' /= localNodeId node ->
577− liftIO $ sendBinary node
578− (ctrlMsgSender msg)
579− (NodeIdentifier nid')
580− WithImplicitReconnect
581− msg
777+ ncSendToNode nid' msg
582778 _ ->
583779 return ()
584780
@@ -599,10 +795,12 @@ nodeController = do
599795 ncEffectRegister from label atnode pid force
600796 NCMsg (ProcessIdentifier from) (WhereIs label) ->
601797 ncEffectWhereIs from label
602− NCMsg from (NamedSend label msg') ->
603− ncEffectNamedSend from label msg'
798+ NCMsg _ (NamedSend label msg') ->
799+ ncEffectNamedSend label msg'
800+ NCMsg _ (UnreliableSend lpid msg') ->
801+ ncEffectLocalSend node (ProcessId (localNodeId node) lpid) msg'
604802 NCMsg _ (LocalSend to msg') ->
605− ncEffectLocalSend to msg'
803+ ncEffectLocalSend node to msg'
606804 NCMsg _ (LocalPortSend to msg') ->
607805 ncEffectLocalPortSend to msg'
608806 NCMsg (ProcessIdentifier from) (Kill to reason) ->
@@ -612,7 +810,11 @@ nodeController = do
612810 NCMsg (ProcessIdentifier from) (GetInfo pid) ->
613811 ncEffectGetInfo from pid
614812 NCMsg _ SigShutdown ->
615− liftIO $ throwIO ThreadKilled -- seems to make more sense than fail/error
813+ liftIO $ do
814+ NT.closeEndPoint (localEndPoint node)
815+ `Exception.finally` throwIO (NodeClosedException $ localNodeId node)
816+ NCMsg (ProcessIdentifier from) (GetNodeStats nid) ->
817+ ncEffectGetNodeStats from nid
616818 unexpected ->
617819 error $ "nodeController: unexpected message " ++ show unexpected
618820
@@ -630,22 +832,18 @@ ncEffectMonitor from them mRef = do
630832 case (shouldLink, isLocal node (ProcessIdentifier from)) of
631833 (True, _) -> -- [Unified: first rule]
632834 case mRef of
633− Just ref -> modify' $ monitorsFor them ^: Set.insert (from, ref)
634− Nothing -> modify' $ linksFor them ^: Set.insert from
835+ Just ref -> modify' $ monitors ^: BiMultiMap.insert them from ref
836+ Nothing -> modify' $ links ^: BiMultiMap.insert them from ()
635837 (False, True) -> -- [Unified: second rule]
636838 notifyDied from them DiedUnknownId mRef
637839 (False, False) -> -- [Unified: third rule]
638840 -- TODO: this is the right sender according to the Unified semantics,
639841 -- but perhaps having 'them' as the sender would make more sense
640842 -- (see also: notifyDied)
641− liftIO $ sendBinary node
642− (NodeIdentifier $ localNodeId node)
643− (NodeIdentifier $ processNodeId from)
644− WithImplicitReconnect
645− NCMsg
646− { ctrlMsgSender = NodeIdentifier (localNodeId node)
647− , ctrlMsgSignal = Died them DiedUnknownId
648− }
843+ ncSendToNode (processNodeId from) $ NCMsg
844+ { ctrlMsgSender = NodeIdentifier (localNodeId node)
845+ , ctrlMsgSignal = Died them DiedUnknownId
846+ }
649847
650848-- [Unified: Table 11]
651849ncEffectUnlink :: ProcessId -> Identifier -> NC ()
@@ -659,7 +857,7 @@ ncEffectUnlink from them = do
659857 postAsMessage from $ DidUnlinkNode nid
660858 SendPortIdentifier cid ->
661859 postAsMessage from $ DidUnlinkPort cid
662− modify' $ linksFor them ^: Set.delete from
860+ modify' $ links ^: BiMultiMap.delete them from ()
663861
664862-- [Unified: Table 11]
665863ncEffectUnmonitor :: ProcessId -> MonitorRef -> NC ()
@@ -667,7 +865,7 @@ ncEffectUnmonitor from ref = do
667865 node <- ask
668866 when (isLocal node (ProcessIdentifier from)) $
669867 postAsMessage from $ DidUnmonitor ref
670− modify' $ monitorsFor (monitorRefIdent ref) ^: Set.delete (from, ref)
868+ modify' $ monitors ^: BiMultiMap.delete (monitorRefIdent ref) from ref
671869
672870-- [Unified: Table 12]
673871ncEffectDied :: Identifier -> DiedReason -> NC ()
@@ -682,7 +880,7 @@ ncEffectDied ident reason = do
682880 let localOnly = case ident of NodeIdentifier _ -> True ; _ -> False
683881
684882 forM_ (Map.toList affectedLinks) $ \(them, uss) ->
685− forM_ uss $ \us ->
883+ forM_ uss $ \(us, _) ->
686884 when (localOnly <= isLocal node (ProcessIdentifier us)) $
687885 notifyDied us them reason Nothing
688886
@@ -691,9 +889,35 @@ ncEffectDied ident reason = do
691889 when (localOnly <= isLocal node (ProcessIdentifier us)) $
692890 notifyDied us them reason (Just ref)
693891
694− modify' $ (links ^= unaffectedLinks) . (monitors ^= unaffectedMons)
695−
696− modify' $ registeredHere ^: Map.filter (\pid -> not $ ident `impliesDeathOf` ProcessIdentifier pid)
892+ -- Notify remote nodes that the process died so it can be removed from monitor
893+ -- lists.
894+ mapM_ (forwardDeath node) $
895+ [ nid | ProcessIdentifier pid <- [ident]
896+ , i <- Set.toList $ Set.union
897+ (Set.map fst $ BiMultiMap.lookupBy2nd pid unaffectedLinks)
898+ (Set.map fst $ BiMultiMap.lookupBy2nd pid unaffectedMons)
899+ , let nid = nodeOf i
900+ , nid /= localNodeId node
901+ ]
902+
903+ -- Delete monitors in the local node.
904+ let deleteDeads :: (Ord a, Ord v)
905+ => BiMultiMap a ProcessId v -> BiMultiMap a ProcessId v
906+ deleteDeads = case ident of
907+ -- deleteAllBy2nd is faster than partitionWithKeyBy2nd
908+ ProcessIdentifier pid -> BiMultiMap.deleteAllBy2nd pid
909+ _ -> snd . BiMultiMap.partitionWithKeyBy2nd
910+ (\pid _ -> ident `impliesDeathOf` ProcessIdentifier pid)
911+ unaffectedLinks' = deleteDeads unaffectedLinks
912+ unaffectedMons' = deleteDeads unaffectedMons
913+
914+ modify' $ (links ^= unaffectedLinks') . (monitors ^= unaffectedMons')
915+
916+ -- we now consider all labels for this identifier unregistered
917+ let toDrop pid = not $ ident `impliesDeathOf` ProcessIdentifier pid
918+ (keepNames, dropNames) <- Map.partition toDrop <$> gets (^. registeredHere)
919+ mapM_ (\(p, l) -> liftIO $ trace node (MxUnRegistered l p)) (Map.toList dropNames)
920+ modify' $ registeredHere ^= keepNames
697921
698922 remaining <- fmap Map.toList (gets (^. registeredOnNodes)) >>=
699923 mapM (\(pid,nidlist) ->
@@ -701,20 +925,15 @@ ncEffectDied ident reason = do
701925 True ->
702926 do forM_ nidlist $ \(nid,_) ->
703927 when (not $ isLocal node (NodeIdentifier nid))
704− (forwardNameDeath node nid)
928+ (forwardDeath node nid)
705929 return Nothing
706930 False -> return $ Just (pid,nidlist) )
707931 modify' $ registeredOnNodes ^= (Map.fromList (catMaybes remaining))
708932 where
709− forwardNameDeath node nid =
710− liftIO $ sendBinary node
711− (NodeIdentifier $ localNodeId node)
712− (NodeIdentifier $ nid)
713− WithImplicitReconnect
714− NCMsg
715− { ctrlMsgSender = NodeIdentifier (localNodeId node)
716− , ctrlMsgSignal = Died ident reason
717− }
933+ forwardDeath node nid = ncSendToNode nid
934+ NCMsg { ctrlMsgSender = NodeIdentifier (localNodeId node)
935+ , ctrlMsgSignal = Died ident reason
936+ }
718937
719938-- [Unified: Table 13]
720939ncEffectSpawn :: ProcessId -> Closure (Process ()) -> SpawnRef -> NC ()
@@ -722,16 +941,13 @@ ncEffectSpawn pid cProc ref = do
722941 mProc <- unClosure cProc
723942 -- If the closure does not exist, we spawn a process that throws an exception
724943 -- This allows the remote node to find out what's happening
944+ -- TODO:
725945 let proc = case mProc of
726946 Left err -> fail $ "Error: Could not resolve closure: " ++ err
727947 Right p -> p
728948 node <- ask
729949 pid' <- liftIO $ forkProcess node proc
730− liftIO $ sendMessage node
731− (NodeIdentifier (localNodeId node))
732− (ProcessIdentifier pid)
733− WithImplicitReconnect
734− (DidSpawn ref pid')
950+ ncSendToProcess pid $ unsafeCreateUnencodedMessage $ DidSpawn ref pid'
735951
736952-- Unified semantics does not explicitly describe how to implement 'register',
737953-- but mentions it's "very similar to nsend" (Table 14)
@@ -749,14 +965,19 @@ ncEffectRegister from label atnode mPid reregistration = do
749965 return $ (isNothing currentVal /= reregistration) &&
750966 (not (isLocal node (ProcessIdentifier thepid) ) || isvalidlocal )
751967 if isLocal node (NodeIdentifier atnode)
752− then do when (isOk) $
968+ then do when isOk $
753969 do modify' $ registeredHereFor label ^= mPid
754970 updateRemote node currentVal mPid
755− liftIO $ sendMessage node
756− (NodeIdentifier (localNodeId node))
757− (ProcessIdentifier from)
758− WithImplicitReconnect
759− (RegisterReply label isOk)
971+ case mPid of
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)
977+ Nothing -> liftIO $ trace node (MxUnRegistered (fromJust currentVal) label)
978+ newVal <- gets (^. registeredHereFor label)
979+ ncSendToProcess from $ unsafeCreateUnencodedMessage $
980+ RegisterReply label isOk newVal
760981 else let operation =
761982 case reregistration of
762983 True -> flip decList
@@ -774,6 +995,7 @@ ncEffectRegister from label atnode mPid reregistration = do
774995 updateRemote _ _ _ = return ()
775996 maybeify f Nothing = unmaybeify $ f []
776997 maybeify f (Just x) = unmaybeify $ f x
998+
777999 unmaybeify [] = Nothing
7781000 unmaybeify x = Just x
7791001 incList [] tag = [(tag,1)]
@@ -785,51 +1007,44 @@ ncEffectRegister from label atnode mPid reregistration = do
7851007 decList (x:xs) tag = x:decList xs tag
7861008 forward node to reg =
7871009 when (not $ isLocal node (NodeIdentifier to)) $
788− liftIO $ sendBinary node
789− (ProcessIdentifier from)
790− (NodeIdentifier to)
791− WithImplicitReconnect
792− NCMsg
793− { ctrlMsgSender = ProcessIdentifier from
794− , ctrlMsgSignal = reg
795− }
1010+ ncSendToNode to $ NCMsg { ctrlMsgSender = ProcessIdentifier from
1011+ , ctrlMsgSignal = reg
1012+ }
7961013
7971014
7981015-- Unified semantics does not explicitly describe 'whereis'
7991016ncEffectWhereIs :: ProcessId -> String -> NC ()
8001017ncEffectWhereIs from label = do
801− node <- ask
8021018 mPid <- gets (^. registeredHereFor label)
803− liftIO $ sendMessage node
804− (NodeIdentifier (localNodeId node))
805− (ProcessIdentifier from)
806− WithImplicitReconnect
807− (WhereIsReply label mPid)
1019+ ncSendToProcess from $ unsafeCreateUnencodedMessage $ WhereIsReply label mPid
8081020
8091021-- [Unified: Table 14]
810−ncEffectNamedSend :: Identifier -> String -> Message -> NC ()
811−ncEffectNamedSend from label msg = do
812− node <- ask
1022+ncEffectNamedSend :: String -> Message -> NC ()
1023+ncEffectNamedSend label msg = do
8131024 mPid <- gets (^. registeredHereFor label)
8141025 -- If mPid is Nothing, we just ignore the named send (as per Table 14)
815− forM_ mPid $ \pid ->
816− liftIO $ sendPayload node
817− from
818− (ProcessIdentifier pid)
819− NoImplicitReconnect
820− (messageToPayload msg)
1026+ forM_ mPid $ \to ->
1027+ -- If this is a trace message we don't trace it to avoid entering a loop
1028+ -- where trace messages produce more trace messages.
1029+ ncSendToProcessAndTrace (label /= "trace.logger") to msg
8211030
8221031-- [Issue #DP-20]
823−ncEffectLocalSend :: ProcessId -> Message -> NC ()
824−ncEffectLocalSend = postMessage
1032+ncEffectLocalSend :: LocalNode -> ProcessId -> Message -> NC ()
1033+ncEffectLocalSend = ncEffectLocalSendAndTrace True
1034+
1035+ncEffectLocalSendAndTrace :: Bool -> LocalNode -> ProcessId -> Message -> NC ()
1036+ncEffectLocalSendAndTrace shouldTrace node to msg =
1037+ liftIO $ withLocalProc node to $ \p -> do
1038+ enqueue (processQueue p) msg
1039+ when shouldTrace $ trace node (MxReceived to msg)
8251040
8261041-- [Issue #DP-20]
8271042ncEffectLocalPortSend :: SendPortId -> Message -> NC ()
828−ncEffectLocalPortSend from msg =
1043+ncEffectLocalPortSend from msg = do
1044+ node <- ask
8291045 let pid = sendPortProcessId from
8301046 cid = sendPortLocalId from
831− in do
832− withLocalProc pid $ \proc -> do
1047+ liftIO $ withLocalProc node pid $ \proc -> do
8331048 mChan <- withMVar (processState proc) $ return . (^. typedChannelWithId cid)
8341049 case mChan of
8351050 -- in the unlikely event we know nothing about this channel id,
@@ -839,11 +1054,12 @@ ncEffectLocalPortSend from msg =
8391054 -- If ch is Nothing, the process has given up the read end of
8401055 -- the channel and we simply ignore the incoming message - this
8411056 ch <- deRefWeak chan'
842− forM_ ch $ \chan -> deliverChan msg chan
843− where deliverChan :: forall a . Message -> TQueue a -> IO ()
844− deliverChan (UnencodedMessage _ raw) chan' =
1057+ forM_ ch $ \chan -> deliverChan node from msg chan
1058+ where deliverChan :: forall a . LocalNode -> SendPortId -> Message -> TQueue a -> IO ()
1059+ deliverChan n p (UnencodedMessage _ raw) chan' = do
8451060 atomically $ writeTQueue chan' ((unsafeCoerce raw) :: a)
846− deliverChan (EncodedMessage _ _) _ =
1061+ trace n (MxReceivedPort p $ unsafeCreateUnencodedMessage raw)
1062+ deliverChan _ _ (EncodedMessage _ _) _ =
8471063 -- this will not happen unless someone screws with Primitives.hs
8481064 error "invalid local channel delivery"
8491065
@@ -868,46 +1084,56 @@ ncEffectGetInfo from pid =
8681084 them = (ProcessIdentifier pid)
8691085 in do
8701086 node <- ask
871− mProc <- liftIO $
872− withMVar (localState node) $ return . (^. localProcessWithId lpid)
1087+ mProc <- liftIO $ withValidLocalState node
1088+ $ return . (^. localProcessWithId lpid)
8731089 case mProc of
8741090 Nothing -> dispatch (isLocal node (ProcessIdentifier from))
875− from node (ProcessInfoNone DiedUnknownId)
876− Just _ -> do
877− itsLinks <- gets (^. linksFor them)
878− itsMons <- gets (^. monitorsFor them)
1091+ from (ProcessInfoNone DiedUnknownId)
1092+ Just proc -> do
1093+ itsLinks <- Set.map fst . BiMultiMap.lookupBy1st them <$>
1094+ gets (^. links)
1095+ itsMons <- BiMultiMap.lookupBy1st them <$> gets (^. monitors)
8791096 registered <- gets (^. registeredHere)
1097+ size <- liftIO $ queueSize $ processQueue $ proc
8801098
8811099 let reg = registeredNames registered
8821100 dispatch (isLocal node (ProcessIdentifier from))
8831101 from
884− node
8851102 ProcessInfo {
8861103 infoNode = (processNodeId pid)
8871104 , infoRegisteredNames = reg
888− -- we cannot populate this field
889− , infoMessageQueueLength = Nothing
890− , infoMonitors = Set.toList itsMons
891− , infoLinks = Set.toList itsLinks
1105+ , infoMessageQueueLength = size
1106+ , infoMonitors = Set.toList itsMons
1107+ , infoLinks = Set.toList itsLinks
8921108 }
893− where dispatch :: (Serializable a, Show a)
1109+ where dispatch :: (Serializable a)
8941110 => Bool
8951111 -> ProcessId
896− -> LocalNode
8971112 -> a
8981113 -> NC ()
899− dispatch True dest _ pInfo = postAsMessage dest $ pInfo
900− dispatch False dest node pInfo = do
901− liftIO $ sendMessage node
902− (NodeIdentifier (localNodeId node))
903− (ProcessIdentifier dest)
904− WithImplicitReconnect
905− pInfo
1114+ dispatch True dest pInfo = postAsMessage dest $ pInfo
1115+ dispatch False dest pInfo =
1116+ ncSendToProcess dest $ unsafeCreateUnencodedMessage pInfo
9061117
9071118 registeredNames = Map.foldlWithKey (\ks k v -> if v == pid
9081119 then (k:ks)
9091120 else ks) []
9101121
1122+ncEffectGetNodeStats :: ProcessId -> NodeId -> NC ()
1123+ncEffectGetNodeStats from _nid = do
1124+ node <- ask
1125+ ncState <- StateT.get
1126+ nodeState <- liftIO $ withValidLocalState node return
1127+ let stats =
1128+ NodeStats {
1129+ nodeStatsNode = localNodeId node
1130+ , nodeStatsRegisteredNames = Map.size $ ncState ^. registeredHere
1131+ , nodeStatsMonitors = BiMultiMap.size $ ncState ^. monitors
1132+ , nodeStatsLinks = BiMultiMap.size $ ncState ^. links
1133+ , nodeStatsProcesses = Map.size (nodeState ^. localProcesses)
1134+ }
1135+ postAsMessage from stats
1136+
9111137--------------------------------------------------------------------------------
9121138-- Auxiliary --
9131139--------------------------------------------------------------------------------
@@ -934,14 +1160,10 @@ notifyDied dest src reason mRef = do
9341160 throwException dest $ PortLinkException pid reason
9351161 (False, _, _) ->
9361162 -- The change in sender comes from [Unified: Table 10]
937− liftIO $ sendBinary node
938− (NodeIdentifier $ localNodeId node)
939− (NodeIdentifier $ processNodeId dest)
940− WithImplicitReconnect
941− NCMsg
942− { ctrlMsgSender = NodeIdentifier (localNodeId node)
943− , ctrlMsgSignal = Died src reason
944− }
1163+ ncSendToNode (processNodeId dest) $ NCMsg
1164+ { ctrlMsgSender = NodeIdentifier (localNodeId node)
1165+ , ctrlMsgSignal = Died src reason
1166+ }
9451167
9461168-- | [Unified: Table 8]
9471169destNid :: ProcessSignal -> Maybe NodeId
@@ -953,6 +1175,7 @@ destNid (Spawn _ _) = Nothing
9531175destNid (Register _ _ _ _) = Nothing
9541176destNid (WhereIs _) = Nothing
9551177destNid (NamedSend _ _) = Nothing
1178+destNid (UnreliableSend _ _) = Nothing
9561179-- We don't need to forward 'Died' signals; if monitoring/linking is setup,
9571180-- then when a local process dies the monitoring/linking machinery will take
9581181-- care of notifying remote nodes
@@ -960,6 +1183,7 @@ destNid (Died _ _) = Nothing
9601183destNid (Kill pid _) = Just $ processNodeId pid
9611184destNid (Exit pid _) = Just $ processNodeId pid
9621185destNid (GetInfo pid) = Just $ processNodeId pid
1186+destNid (GetNodeStats nid) = Just nid
9631187destNid (LocalSend pid _) = Just $ processNodeId pid
9641188destNid (LocalPortSend cid _) = Just $ processNodeId (sendPortProcessId cid)
9651189destNid (SigShutdown) = Nothing
@@ -978,7 +1202,7 @@ unClosure closure = do
9781202isValidLocalIdentifier :: Identifier -> NC Bool
9791203isValidLocalIdentifier ident = do
9801204 node <- ask
981− liftIO . withMVar (localState node) $ \nSt ->
1205+ liftIO . withValidLocalState node $ \nSt ->
9821206 case ident of
9831207 NodeIdentifier nid ->
9841208 return $ nid == localNodeId node
@@ -1003,30 +1227,33 @@ postAsMessage pid = postMessage pid . createUnencodedMessage
10031227
10041228postMessage :: ProcessId -> Message -> NC ()
10051229postMessage pid msg = do
1006− withLocalProc pid $ \p -> enqueue (processQueue p) msg
1230+ node <- ask
1231+ liftIO $ withLocalProc node pid $ \p -> enqueue (processQueue p) msg
10071232
10081233throwException :: Exception e => ProcessId -> e -> NC ()
1009−throwException pid e = withLocalProc pid $ \p ->
1010− throwTo (processThread p) e
1011−
1012−withLocalProc :: ProcessId -> (LocalProcess -> IO ()) -> NC ()
1013−withLocalProc pid p = do
1234+throwException pid e = do
10141235 node <- ask
1015− liftIO $ do
1016− -- By [Unified: table 6, rule missing_process] messages to dead processes
1017− -- can silently be dropped
1018− let lpid = processLocalId pid
1019− mProc <- withMVar (localState node) $ return . (^. localProcessWithId lpid)
1020− forM_ mProc p
1236+ -- throwTo blocks until the exception is received by the target thread.
1237+ -- We cannot easily make it happen asynchronpusly because then 'unlink'
1238+ -- semantics would break.
1239+ liftIO $ withLocalProc node pid $ \p -> throwTo (processThread p) e
1240+
1241+withLocalProc :: LocalNode -> ProcessId -> (LocalProcess -> IO ()) -> IO ()
1242+withLocalProc node pid p =
1243+ -- By [Unified: table 6, rule missing_process] messages to dead processes
1244+ -- can silently be dropped
1245+ let lpid = processLocalId pid in do
1246+ join $ withValidLocalState node $ \vst ->
1247+ return $ forM_ (vst ^. localProcessWithId lpid) p
10211248
10221249--------------------------------------------------------------------------------
10231250-- Accessors --
10241251--------------------------------------------------------------------------------
10251252
1026−links :: Accessor NCState (Map Identifier (Set ProcessId))
1253+links :: Accessor NCState (BiMultiMap Identifier ProcessId ())
10271254links = accessor _links (\ls st -> st { _links = ls })
10281255
1029−monitors :: Accessor NCState (Map Identifier (Set (ProcessId, MonitorRef)))
1256+monitors :: Accessor NCState (BiMultiMap Identifier ProcessId MonitorRef)
10301257monitors = accessor _monitors (\ms st -> st { _monitors = ms })
10311258
10321259registeredHere :: Accessor NCState (Map String ProcessId)
@@ -1035,12 +1262,6 @@ registeredHere = accessor _registeredHere (\ry st -> st { _registeredHere = ry }
10351262registeredOnNodes :: Accessor NCState (Map ProcessId [(NodeId, Int)])
10361263registeredOnNodes = accessor _registeredOnNodes (\ry st -> st { _registeredOnNodes = ry })
10371264
1038−linksFor :: Identifier -> Accessor NCState (Set ProcessId)
1039−linksFor ident = links >>> DAC.mapDefault Set.empty ident
1040−
1041−monitorsFor :: Identifier -> Accessor NCState (Set (ProcessId, MonitorRef))
1042−monitorsFor ident = monitors >>> DAC.mapDefault Set.empty ident
1043−
10441265registeredHereFor :: String -> Accessor NCState (Maybe ProcessId)
10451266registeredHereFor ident = registeredHere >>> DAC.mapMaybe ident
10461267
@@ -1067,10 +1288,12 @@ registeredOnNodesFor ident = registeredOnNodes >>> DAC.mapMaybe ident
10671288-- * the notifications for typed channels to that process.
10681289--
10691290-- See https://github.com/haskell/containers/issues/14 for the bang on _v.
1070−splitNotif :: Identifier
1071− -> Map Identifier a
1072− -> (Map Identifier a, Map Identifier a)
1073−splitNotif ident = Map.partitionWithKey (\k !_v -> ident `impliesDeathOf` k)
1291+splitNotif :: (Ord a, Ord v)
1292+ => Identifier
1293+ -> BiMultiMap Identifier a v
1294+ -> (Map Identifier (Set (a,v)), BiMultiMap Identifier a v)
1295+splitNotif ident =
1296+ BiMultiMap.partitionWithKeyBy1st (\k !_v -> ident `impliesDeathOf` k)
10741297
10751298--------------------------------------------------------------------------------
10761299-- Auxiliary --
src/Control/Distributed/Process/Serializable.hs+18−8
@@ -1,3 +1,8 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE UndecidableInstances #-}
3+{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
5+{-# LANGUAGE GADTs #-}
16module Control.Distributed.Process.Serializable
27 ( Serializable
38 , encodeFingerprint
@@ -7,28 +12,33 @@ module Control.Distributed.Process.Serializable
712 , Fingerprint
813 , showFingerprint
914 , SerializableDict(SerializableDict)
15+ , TypeableDict(TypeableDict)
1016 ) where
1117
1218import Data.Binary (Binary)
13−import Data.Typeable (Typeable(..))
14−import Data.Typeable.Internal (TypeRep(TypeRep))
19+
20+import Data.Typeable (Typeable, typeRepFingerprint, typeOf)
21+
1522import Numeric (showHex)
1623import Control.Exception (throw)
1724import GHC.Fingerprint.Type (Fingerprint(..))
1825import Data.ByteString (ByteString)
1926import qualified Data.ByteString as BS
20−import qualified Data.ByteString.Internal as BSI ( unsafeCreate
21− , inlinePerformIO
22− , toForeignPtr
23− )
27+import qualified Data.ByteString.Internal as BSI ( unsafeCreate, toForeignPtr )
2428import Foreign.Storable (pokeByteOff, peekByteOff, sizeOf)
2529import Foreign.ForeignPtr (withForeignPtr)
30+import System.IO.Unsafe (unsafePerformIO)
2631
2732-- | Reification of 'Serializable' (see "Control.Distributed.Process.Closure")
2833data SerializableDict a where
2934 SerializableDict :: Serializable a => SerializableDict a
3035 deriving (Typeable)
3136
37+-- | Reification of 'Typeable'.
38+data TypeableDict a where
39+ TypeableDict :: Typeable a => TypeableDict a
40+ deriving (Typeable)
41+
3242-- | Objects that can be sent across the network
3343class (Binary a, Typeable a) => Serializable a
3444instance (Binary a, Typeable a) => Serializable a
@@ -45,7 +55,7 @@ decodeFingerprint :: ByteString -> Fingerprint
4555decodeFingerprint bs
4656 | BS.length bs /= sizeOfFingerprint =
4757 throw $ userError "decodeFingerprint: Invalid length"
48− | otherwise = BSI.inlinePerformIO $ do
58+ | otherwise = unsafePerformIO $ do
4959 let (fp, offset, _) = BSI.toForeignPtr bs
5060 withForeignPtr fp $ \p -> peekByteOff p offset
5161
@@ -55,7 +65,7 @@ sizeOfFingerprint = sizeOf (undefined :: Fingerprint)
5565
5666-- | The fingerprint of the typeRep of the argument
5767fingerprint :: Typeable a => a -> Fingerprint
58−fingerprint a = let TypeRep fp _ _ = typeOf a in fp
68+fingerprint = typeRepFingerprint . typeOf
5969
6070-- | Show fingerprint (for debugging purposes)
6171showFingerprint :: Fingerprint -> ShowS
src/Control/Distributed/Process/UnsafePrimitives.hsnew file+200
@@ -0,0 +1,200 @@
1+-----------------------------------------------------------------------------
2+-- |
3+-- Module : Control.Distributed.Process.UnsafePrimitives
4+-- Copyright : (c) Well-Typed / Tim Watson
5+-- License : BSD3 (see the file LICENSE)
6+--
7+-- Maintainer : Tim Watson <watson.timothy@gmail.com>
8+-- Stability : experimental
9+-- Portability : non-portable (requires concurrency)
10+--
11+-- [Unsafe Variants of Cloud Haskell's Messaging Primitives]
12+--
13+-- Cloud Haskell's semantics do not mention evaluation/strictness properties
14+-- at all; Only the promise (or lack) of signal/message delivery between
15+-- processes is considered. For practical purposes, Cloud Haskell optimises
16+-- communication between (intra-node) local processes by skipping the
17+-- network-transport layer. In order to ensure the same strictness semantics
18+-- however, messages /still/ undergo binary serialisation before (internal)
19+-- transmission takes place. Thus we ensure that in both (the local and remote)
20+-- cases, message payloads are fully evaluated. Whilst this provides the user
21+-- with /unsurprising/ behaviour, the resulting performance overhead is quite
22+-- severe. Communicating data between Haskell threads /without/ forcing binary
23+-- serialisation reduces (intra-node, inter-process) communication overheads
24+-- by several orders of magnitude.
25+--
26+-- This module provides variants of Cloud Haskell's messaging primitives
27+-- ('send', 'sendChan', 'nsend' and 'wrapMessage') which do /not/ force binary
28+-- serialisation in the local case, thereby offering superior intra-node
29+-- messaging performance. The /catch/ is that any evaluation problems lurking
30+-- within the passed data structure (e.g., fields set to @undefined@ and so on)
31+-- will show up in the receiver rather than in the caller (as they would with
32+-- the /normal/ strategy).
33+--
34+-- Use of the functions in this module can potentially change the runtime
35+-- behaviour of your application. In addition, messages passed between Cloud
36+-- Haskell processes are written to a tracing infrastructure on the local node,
37+-- to provide improved introspection and debugging facilities for complex actor
38+-- based systems. This module makes no attempt to force evaluation in these
39+-- cases either, thus evaluation problems in passed data structures could not
40+-- only crash your processes, but could also bring down critical internal
41+-- services on which the node relies to function correctly.
42+--
43+-- If you wish to repudiate such issues, you are advised to consider the use
44+-- of NFSerialisable in the distributed-process-extras package, which type
45+-- class brings NFData into scope along with Serializable, such that we can
46+-- force evaluation. Intended for use with modules such as this one, this
47+-- approach guarantees correct evaluatedness in terms of @NFData@. Please note
48+-- however, that we /cannot/ guarantee that an @NFData@ instance will behave the
49+-- same way as a @Binary@ one with regards evaluation, so it is still possible
50+-- to introduce unexpected behaviour by using /unsafe/ primitives in this way.
51+--
52+-- You have been warned!
53+--
54+-- This module is exported so that you can replace the use of Cloud Haskell's
55+-- /safe/ messaging primitives. If you want to use both variants, then you can
56+-- take advantage of qualified imports, however "Control.Distributed.Process"
57+-- also re-exports these functions under different names, using the @unsafe@
58+-- prefix.
59+--
60+module Control.Distributed.Process.UnsafePrimitives
61+ ( -- * Unsafe Basic Messaging
62+ send
63+ , sendChan
64+ , nsend
65+ , nsendRemote
66+ , usend
67+ , wrapMessage
68+ ) where
69+
70+import Control.Distributed.Process.Internal.Messaging
71+ ( sendMessage
72+ , sendBinary
73+ , sendCtrlMsg
74+ )
75+import Control.Distributed.Process.Management.Internal.Types
76+ ( MxEvent(..)
77+ )
78+import Control.Distributed.Process.Management.Internal.Trace.Types
79+ ( traceEvent
80+ )
81+import Control.Distributed.Process.Internal.Types
82+ ( ProcessId(..)
83+ , NodeId(..)
84+ , LocalNode(..)
85+ , LocalProcess(..)
86+ , Process(..)
87+ , SendPort(..)
88+ , ProcessSignal(..)
89+ , Identifier(..)
90+ , ImplicitReconnect(..)
91+ , SendPortId(..)
92+ , Message
93+ , createMessage
94+ , sendPortProcessId
95+ , unsafeCreateUnencodedMessage
96+ )
97+import Control.Distributed.Process.Serializable (Serializable)
98+
99+import Control.Monad.IO.Class (liftIO)
100+import Control.Monad.Reader (ask)
101+
102+-- | Named send to a process in the local registry (asynchronous)
103+nsend :: Serializable a => String -> a -> Process ()
104+nsend label msg = do
105+ proc <- ask
106+ let us = processId proc
107+ let msg' = wrapMessage msg
108+ -- see [note: tracing]
109+ liftIO $ traceEvent (localEventBus (processNode proc))
110+ (MxSentToName label us msg')
111+ sendCtrlMsg Nothing (NamedSend label msg')
112+
113+-- | Named send to a process in a remote registry (asynchronous)
114+nsendRemote :: Serializable a => NodeId -> String -> a -> Process ()
115+nsendRemote nid label msg = do
116+ proc <- ask
117+ let us = processId proc
118+ let node = processNode proc
119+ if localNodeId node == nid
120+ then nsend label msg
121+ else
122+ let lbl = label ++ "@" ++ show nid in do
123+ -- see [note: tracing] NB: this is a remote call to another NC...
124+ liftIO $ traceEvent (localEventBus node)
125+ (MxSentToName lbl us (wrapMessage msg))
126+ sendCtrlMsg (Just nid) (NamedSend label (createMessage msg))
127+
128+-- | Send a message
129+send :: Serializable a => ProcessId -> a -> Process ()
130+send them msg = do
131+ proc <- ask
132+ let node = localNodeId (processNode proc)
133+ destNode = (processNodeId them)
134+ us = (processId proc)
135+ msg' = wrapMessage msg in do
136+ -- see [note: tracing]
137+ liftIO $ traceEvent (localEventBus (processNode proc))
138+ (MxSent them us msg')
139+ if destNode == node
140+ then sendCtrlMsg Nothing $ LocalSend them msg'
141+ else liftIO $ sendMessage (processNode proc)
142+ (ProcessIdentifier (processId proc))
143+ (ProcessIdentifier them)
144+ NoImplicitReconnect
145+ msg
146+
147+-- | Send a message unreliably.
148+--
149+-- Unlike 'send', this function is insensitive to 'reconnect'. It will
150+-- try to send the message regardless of the history of connection failures
151+-- between the nodes.
152+--
153+-- Message passing with 'usend' is ordered for a given sender and receiver
154+-- if the messages arrive at all.
155+--
156+usend :: Serializable a => ProcessId -> a -> Process ()
157+usend them msg = do
158+ proc <- ask
159+ let there = processNodeId them
160+ let (us, node) = (processId proc, processNode proc)
161+ let msg' = wrapMessage msg
162+ -- see [note: tracing]
163+ liftIO $ traceEvent (localEventBus node) (MxSent them us msg')
164+ if localNodeId (processNode proc) == there
165+ then sendCtrlMsg Nothing $ LocalSend them msg'
166+ else sendCtrlMsg (Just there) $ UnreliableSend (processLocalId them)
167+ (createMessage msg)
168+
169+-- [note: tracing]
170+-- Note that tracing writes to the local node's control channel, and this
171+-- module explicitly specifies to its clients that it does unsafe message
172+-- encoding. The same is true for the messages it puts onto the Management
173+-- event bus, however we do *not* want unevaluated thunks hitting the event
174+-- bus control thread. Hence the word /Unsafe/ in this module's name!
175+--
176+
177+-- | Send a message on a typed channel
178+sendChan :: Serializable a => SendPort a -> a -> Process ()
179+sendChan (SendPort cid) msg = do
180+ proc <- ask
181+ let node = processNode proc
182+ pid = processId proc
183+ us = localNodeId node
184+ them = processNodeId (sendPortProcessId cid)
185+ msg' = wrapMessage msg in do
186+ liftIO $ traceEvent (localEventBus node) (MxSentToPort pid cid msg')
187+ if them == us
188+ then unsafeSendChanLocal cid msg' -- NB: we wrap to P.Message !!!
189+ else liftIO $ sendBinary node
190+ (ProcessIdentifier pid)
191+ (SendPortIdentifier cid)
192+ NoImplicitReconnect
193+ msg
194+ where
195+ unsafeSendChanLocal :: SendPortId -> Message -> Process ()
196+ unsafeSendChanLocal p m = sendCtrlMsg Nothing $ LocalPortSend p m
197+
198+-- | Create an unencoded @Message@ for any @Serializable@ type.
199+wrapMessage :: Serializable a => a -> Message
200+wrapMessage = unsafeCreateUnencodedMessage
stack-ghc-7.10.3.yamlnew file+21
@@ -0,0 +1,21 @@
1+resolver: nightly-2016-03-08
2+
3+packages:
4+- '.'
5+- distributed-process-tests/
6+- location:
7+ git: https://github.com/haskell-distributed/network-transport-tcp.git
8+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
9+ extra-dep: true
10+
11+extra-deps:
12+- rank1dynamic-0.4.0
13+- distributed-static-0.3.8
14+- network-transport-0.5.2
15+- network-transport-inmemory-0.5.2
16+- rematch-0.2.0.0
17+- exceptions-0.8.2.1 # test dependency
18+
19+flags:
20+ distributed-process-tests:
21+ tcp: true
stack-ghc-8.0.2.yamlnew file+23
@@ -0,0 +1,23 @@
1+resolver: lts-9.21
2+# added -location for network-transport-tcp-0.6.0
3+# since c7fd79 broke the client facing API for createTransport :/
4+
5+packages:
6+- '.'
7+- distributed-process-tests/
8+- location:
9+ git: https://github.com/haskell-distributed/network-transport-tcp.git
10+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
11+ extra-dep: true
12+
13+extra-deps:
14+- rank1dynamic-0.4.0
15+- distributed-static-0.3.8
16+- network-transport-0.5.2
17+- network-transport-inmemory-0.5.2
18+- rematch-0.2.0.0
19+# - exceptions-0.8.2.1 # test dependency
20+
21+flags:
22+ distributed-process-tests:
23+ tcp: true
stack-ghc-8.2.2.yamlnew file+21
@@ -0,0 +1,21 @@
1+resolver: lts-11.22
2+
3+packages:
4+- '.'
5+- distributed-process-tests/
6+- location:
7+ git: https://github.com/haskell-distributed/network-transport-tcp.git
8+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
9+ extra-dep: true
10+
11+extra-deps:
12+- rank1dynamic-0.4.0
13+- distributed-static-0.3.8
14+- network-transport-0.5.2
15+- network-transport-inmemory-0.5.2
16+- rematch-0.2.0.0
17+# - exceptions-0.8.2.1 # test dependency
18+
19+flags:
20+ distributed-process-tests:
21+ tcp: true
stack-ghc-8.4.4.yamlnew file+20
@@ -0,0 +1,20 @@
1+resolver: lts-12.18
2+
3+packages:
4+- '.'
5+- distributed-process-tests/
6+- location:
7+ git: https://github.com/haskell-distributed/network-transport-tcp.git
8+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
9+ extra-dep: true
10+
11+extra-deps:
12+- rank1dynamic-0.4.0
13+- distributed-static-0.3.8
14+- network-transport-0.5.2
15+- network-transport-inmemory-0.5.2
16+- rematch-0.2.0.0
17+
18+flags:
19+ distributed-process-tests:
20+ tcp: true
stack-nightly.yamlnew file+26
@@ -0,0 +1,26 @@
1+resolver: nightly
2+
3+packages:
4+- '.'
5+- distributed-process-tests/
6+- location:
7+ git: https://github.com/haskell-distributed/distributed-static.git
8+ commit: c51e6aadd3a44c044e96a8e986c11d3a94b45b11
9+ extra-dep: true
10+- location:
11+ git: https://github.com/haskell-distributed/network-transport-tcp.git
12+ commit: d58250e2388b86a1865ac4139373cbdc106e72e1
13+ extra-dep: true
14+- location:
15+ git: https://github.com/haskell-distributed/network-transport-inmemory.git
16+ commit: 0ce97924f15a8c1570b2355151834305c9bd2a28
17+ extra-dep: true
18+
19+extra-deps:
20+- rank1dynamic-0.4.0
21+- network-transport-0.5.2
22+- rematch-0.2.0.0
23+
24+flags:
25+ distributed-process-tests:
26+ tcp: true
stack.yamlnew file+22
@@ -0,0 +1,22 @@
1+resolver: lts-12.18 # Use GHC 8.4.4
2+
3+# TODO: we can enable nightly-2018-11-11 once PR #319 is merged
4+
5+packages:
6+- '.'
7+- distributed-process-tests/
8+- location:
9+ git: https://github.com/haskell-distributed/network-transport-tcp.git
10+ commit: d87d6f55697a94a1fbf211ff9c1bb769a1e129cd
11+ extra-dep: true
12+
13+extra-deps:
14+- rank1dynamic-0.4.0
15+- distributed-static-0.3.8
16+- network-transport-0.5.2
17+- network-transport-inmemory-0.5.2
18+- rematch-0.2.0.0
19+
20+flags:
21+ distributed-process-tests:
22+ tcp: true
tests/TestCH.hsdeleted−1213
@@ -1,1213 +0,0 @@
1−module Main where
2−
3−#if ! MIN_VERSION_base(4,6,0)
4−import Prelude hiding (catch)
5−#endif
6−
7−import Data.Binary (Binary(..))
8−import Data.Typeable (Typeable)
9−import Data.Foldable (forM_)
10−import Control.Concurrent (forkIO, threadDelay, myThreadId, throwTo, ThreadId)
11−import Control.Concurrent.MVar
12− ( MVar
13− , newEmptyMVar
14− , putMVar
15− , takeMVar
16− , readMVar
17− )
18−import Control.Monad (replicateM_, replicateM, forever)
19−import Control.Exception (SomeException, throwIO)
20−import qualified Control.Exception as Ex (catch)
21−import Control.Applicative ((<$>), (<*>), pure, (<|>))
22−import qualified Network.Transport as NT (Transport, closeEndPoint)
23−import Network.Socket (sClose)
24−import Network.Transport.TCP
25− ( createTransportExposeInternals
26− , TransportInternals(socketBetween)
27− , defaultTCPParameters
28− )
29−import Control.Distributed.Process
30−import Control.Distributed.Process.Node
31−import Control.Distributed.Process.Serializable (Serializable)
32−
33−import Test.HUnit (Assertion)
34−import Test.Framework (Test, defaultMain, testGroup)
35−import Test.Framework.Providers.HUnit (testCase)
36−
37−newtype Ping = Ping ProcessId
38− deriving (Typeable, Binary, Show)
39−
40−newtype Pong = Pong ProcessId
41− deriving (Typeable, Binary, Show)
42−
43−--------------------------------------------------------------------------------
44−-- Supporting definitions --
45−--------------------------------------------------------------------------------
46−
47−-- | Like fork, but throw exceptions in the child thread to the parent
48−forkTry :: IO () -> IO ThreadId
49−forkTry p = do
50− tid <- myThreadId
51− forkIO $ Ex.catch p (\e -> throwTo tid (e :: SomeException))
52−
53−-- | The ping server from the paper
54−ping :: Process ()
55−ping = do
56− Pong partner <- expect
57− self <- getSelfPid
58− send partner (Ping self)
59− ping
60−
61−-- | Quick and dirty synchronous version of whereisRemoteAsync
62−whereisRemote :: NodeId -> String -> Process (Maybe ProcessId)
63−whereisRemote nid string = do
64− whereisRemoteAsync nid string
65− WhereIsReply _ mPid <- expect
66− return mPid
67−
68−data Add = Add ProcessId Double Double deriving (Typeable)
69−data Divide = Divide ProcessId Double Double deriving (Typeable)
70−data DivByZero = DivByZero deriving (Typeable)
71−
72−instance Binary Add where
73− put (Add pid x y) = put pid >> put x >> put y
74− get = Add <$> get <*> get <*> get
75−
76−instance Binary Divide where
77− put (Divide pid x y) = put pid >> put x >> put y
78− get = Divide <$> get <*> get <*> get
79−
80−instance Binary DivByZero where
81− put DivByZero = return ()
82− get = return DivByZero
83−
84−-- The math server from the paper
85−math :: Process ()
86−math = do
87− receiveWait
88− [ match (\(Add pid x y) -> send pid (x + y))
89− , matchIf (\(Divide _ _ y) -> y /= 0)
90− (\(Divide pid x y) -> send pid (x / y))
91− , match (\(Divide pid _ _) -> send pid DivByZero)
92− ]
93− math
94−
95−-- | Monitor or link to a remote node
96−monitorOrLink :: Bool -- ^ 'True' for monitor, 'False' for link
97− -> ProcessId -- Process to monitor/link to
98− -> Maybe (MVar ()) -- MVar to signal on once the monitor has been set up
99− -> Process (Maybe MonitorRef)
100−monitorOrLink mOrL pid mSignal = do
101− result <- if mOrL then Just <$> monitor pid
102− else link pid >> return Nothing
103− -- Monitor is asynchronous, which usually does not matter but if we want a
104− -- *specific* signal then it does. Therefore we wait an arbitrary delay and
105− -- hope that this means the monitor has been set up
106− forM_ mSignal $ \signal -> liftIO . forkIO $ threadDelay 100000 >> putMVar signal ()
107− return result
108−
109−monitorTestProcess :: ProcessId -- Process to monitor/link to
110− -> Bool -- 'True' for monitor, 'False' for link
111− -> Bool -- Should we unmonitor?
112− -> DiedReason -- Expected cause of death
113− -> Maybe (MVar ()) -- Signal for 'monitor set up'
114− -> MVar () -- Signal for successful termination
115− -> Process ()
116−monitorTestProcess theirAddr mOrL un reason monitorSetup done =
117− catch (do mRef <- monitorOrLink mOrL theirAddr monitorSetup
118− case (un, mRef) of
119− (True, Nothing) -> do
120− unlink theirAddr
121− liftIO $ putMVar done ()
122− (True, Just ref) -> do
123− unmonitor ref
124− liftIO $ putMVar done ()
125− (False, ref) -> do
126− ProcessMonitorNotification ref' pid reason' <- expect
127− True <- return $ Just ref' == ref && pid == theirAddr && mOrL && reason == reason'
128− liftIO $ putMVar done ()
129− )
130− (\(ProcessLinkException pid reason') -> do
131− True <- return $ pid == theirAddr && not mOrL && not un && reason == reason'
132− liftIO $ putMVar done ()
133− )
134−
135−--------------------------------------------------------------------------------
136−-- The tests proper --
137−--------------------------------------------------------------------------------
138−
139−-- | Basic ping test
140−testPing :: NT.Transport -> Assertion
141−testPing transport = do
142− serverAddr <- newEmptyMVar
143− clientDone <- newEmptyMVar
144−
145− -- Server
146− forkIO $ do
147− localNode <- newLocalNode transport initRemoteTable
148− addr <- forkProcess localNode ping
149− putMVar serverAddr addr
150−
151− -- Client
152− forkIO $ do
153− localNode <- newLocalNode transport initRemoteTable
154− pingServer <- readMVar serverAddr
155−
156− let numPings = 10000
157−
158− runProcess localNode $ do
159− pid <- getSelfPid
160− replicateM_ numPings $ do
161− send pingServer (Pong pid)
162− Ping _ <- expect
163− return ()
164−
165− putMVar clientDone ()
166−
167− takeMVar clientDone
168−
169−-- | Monitor a process on an unreachable node
170−testMonitorUnreachable :: NT.Transport -> Bool -> Bool -> Assertion
171−testMonitorUnreachable transport mOrL un = do
172− deadProcess <- newEmptyMVar
173− done <- newEmptyMVar
174−
175− forkIO $ do
176− localNode <- newLocalNode transport initRemoteTable
177− addr <- forkProcess localNode . liftIO $ threadDelay 1000000
178− closeLocalNode localNode
179− putMVar deadProcess addr
180−
181− forkIO $ do
182− localNode <- newLocalNode transport initRemoteTable
183− theirAddr <- readMVar deadProcess
184− runProcess localNode $
185− monitorTestProcess theirAddr mOrL un DiedDisconnect Nothing done
186−
187− takeMVar done
188−
189−-- | Monitor a process which terminates normally
190−testMonitorNormalTermination :: NT.Transport -> Bool -> Bool -> Assertion
191−testMonitorNormalTermination transport mOrL un = do
192− monitorSetup <- newEmptyMVar
193− monitoredProcess <- newEmptyMVar
194− done <- newEmptyMVar
195−
196− forkIO $ do
197− localNode <- newLocalNode transport initRemoteTable
198− addr <- forkProcess localNode $
199− liftIO $ readMVar monitorSetup
200− putMVar monitoredProcess addr
201−
202− forkIO $ do
203− localNode <- newLocalNode transport initRemoteTable
204− theirAddr <- readMVar monitoredProcess
205− runProcess localNode $
206− monitorTestProcess theirAddr mOrL un DiedNormal (Just monitorSetup) done
207−
208− takeMVar done
209−
210−-- | Monitor a process which terminates abnormally
211−testMonitorAbnormalTermination :: NT.Transport -> Bool -> Bool -> Assertion
212−testMonitorAbnormalTermination transport mOrL un = do
213− monitorSetup <- newEmptyMVar
214− monitoredProcess <- newEmptyMVar
215− done <- newEmptyMVar
216−
217− let err = userError "Abnormal termination"
218−
219− forkIO $ do
220− localNode <- newLocalNode transport initRemoteTable
221− addr <- forkProcess localNode . liftIO $ do
222− readMVar monitorSetup
223− throwIO err
224− putMVar monitoredProcess addr
225−
226− forkIO $ do
227− localNode <- newLocalNode transport initRemoteTable
228− theirAddr <- readMVar monitoredProcess
229− runProcess localNode $
230− monitorTestProcess theirAddr mOrL un (DiedException (show err)) (Just monitorSetup) done
231−
232− takeMVar done
233−
234−-- | Monitor a local process that is already dead
235−testMonitorLocalDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion
236−testMonitorLocalDeadProcess transport mOrL un = do
237− processDead <- newEmptyMVar
238− processAddr <- newEmptyMVar
239− localNode <- newLocalNode transport initRemoteTable
240− done <- newEmptyMVar
241−
242− forkIO $ do
243− addr <- forkProcess localNode . liftIO $ putMVar processDead ()
244− putMVar processAddr addr
245−
246− forkIO $ do
247− theirAddr <- readMVar processAddr
248− readMVar processDead
249− runProcess localNode $ do
250− monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
251−
252− takeMVar done
253−
254−-- | Monitor a remote process that is already dead
255−testMonitorRemoteDeadProcess :: NT.Transport -> Bool -> Bool -> Assertion
256−testMonitorRemoteDeadProcess transport mOrL un = do
257− processDead <- newEmptyMVar
258− processAddr <- newEmptyMVar
259− done <- newEmptyMVar
260−
261− forkIO $ do
262− localNode <- newLocalNode transport initRemoteTable
263− addr <- forkProcess localNode . liftIO $ putMVar processDead ()
264− putMVar processAddr addr
265−
266− forkIO $ do
267− localNode <- newLocalNode transport initRemoteTable
268− theirAddr <- readMVar processAddr
269− readMVar processDead
270− runProcess localNode $ do
271− monitorTestProcess theirAddr mOrL un DiedUnknownId Nothing done
272−
273− takeMVar done
274−
275−-- | Monitor a process that becomes disconnected
276−testMonitorDisconnect :: NT.Transport -> Bool -> Bool -> Assertion
277−testMonitorDisconnect transport mOrL un = do
278− processAddr <- newEmptyMVar
279− monitorSetup <- newEmptyMVar
280− done <- newEmptyMVar
281−
282− forkIO $ do
283− localNode <- newLocalNode transport initRemoteTable
284− addr <- forkProcess localNode . liftIO $ threadDelay 1000000
285− putMVar processAddr addr
286− readMVar monitorSetup
287− NT.closeEndPoint (localEndPoint localNode)
288−
289− forkIO $ do
290− localNode <- newLocalNode transport initRemoteTable
291− theirAddr <- readMVar processAddr
292− runProcess localNode $ do
293− monitorTestProcess theirAddr mOrL un DiedDisconnect (Just monitorSetup) done
294−
295− takeMVar done
296−
297−-- | Test the math server (i.e., receiveWait)
298−testMath :: NT.Transport -> Assertion
299−testMath transport = do
300− serverAddr <- newEmptyMVar
301− clientDone <- newEmptyMVar
302−
303− -- Server
304− forkIO $ do
305− localNode <- newLocalNode transport initRemoteTable
306− addr <- forkProcess localNode math
307− putMVar serverAddr addr
308−
309− -- Client
310− forkIO $ do
311− localNode <- newLocalNode transport initRemoteTable
312− mathServer <- readMVar serverAddr
313−
314− runProcess localNode $ do
315− pid <- getSelfPid
316− send mathServer (Add pid 1 2)
317− 3 <- expect :: Process Double
318− send mathServer (Divide pid 8 2)
319− 4 <- expect :: Process Double
320− send mathServer (Divide pid 8 0)
321− DivByZero <- expect
322− liftIO $ putMVar clientDone ()
323−
324− takeMVar clientDone
325−
326−-- | Send first message (i.e. connect) to an already terminated process
327−-- (without monitoring); then send another message to a second process on
328−-- the same remote node (we're checking that the remote node did not die)
329−testSendToTerminated :: NT.Transport -> Assertion
330−testSendToTerminated transport = do
331− serverAddr1 <- newEmptyMVar
332− serverAddr2 <- newEmptyMVar
333− clientDone <- newEmptyMVar
334−
335− forkIO $ do
336− terminated <- newEmptyMVar
337− localNode <- newLocalNode transport initRemoteTable
338− addr1 <- forkProcess localNode $ liftIO $ putMVar terminated ()
339− addr2 <- forkProcess localNode $ ping
340− readMVar terminated
341− putMVar serverAddr1 addr1
342− putMVar serverAddr2 addr2
343−
344− forkIO $ do
345− localNode <- newLocalNode transport initRemoteTable
346− server1 <- readMVar serverAddr1
347− server2 <- readMVar serverAddr2
348− runProcess localNode $ do
349− pid <- getSelfPid
350− send server1 "Hi"
351− send server2 (Pong pid)
352− Ping pid' <- expect
353− True <- return $ pid' == server2
354− liftIO $ putMVar clientDone ()
355−
356− takeMVar clientDone
357−
358−-- | Test (non-zero) timeout
359−testTimeout :: NT.Transport -> Assertion
360−testTimeout transport = do
361− localNode <- newLocalNode transport initRemoteTable
362− done <- newEmptyMVar
363−
364− runProcess localNode $ do
365− Nothing <- receiveTimeout 1000000 [match (\(Add _ _ _) -> return ())]
366− liftIO $ putMVar done ()
367−
368− takeMVar done
369−
370−-- | Test zero timeout
371−testTimeout0 :: NT.Transport -> Assertion
372−testTimeout0 transport = do
373− serverAddr <- newEmptyMVar
374− clientDone <- newEmptyMVar
375− messagesSent <- newEmptyMVar
376−
377− forkIO $ do
378− localNode <- newLocalNode transport initRemoteTable
379− addr <- forkProcess localNode $ do
380− liftIO $ readMVar messagesSent >> threadDelay 1000000
381− -- Variation on the venerable ping server which uses a zero timeout
382− -- Since we wait for all messages to be sent before doing this receive,
383− -- we should nevertheless find the right message immediately
384− Just partner <- receiveTimeout 0 [match (\(Pong partner) -> return partner)]
385− self <- getSelfPid
386− send partner (Ping self)
387− putMVar serverAddr addr
388−
389− forkIO $ do
390− localNode <- newLocalNode transport initRemoteTable
391− server <- readMVar serverAddr
392− runProcess localNode $ do
393− pid <- getSelfPid
394− -- Send a bunch of messages. A large number of messages that the server
395− -- is not interested in, and then a single message that it wants
396− replicateM_ 10000 $ send server "Irrelevant message"
397− send server (Pong pid)
398− liftIO $ putMVar messagesSent ()
399− Ping _ <- expect
400− liftIO $ putMVar clientDone ()
401−
402− takeMVar clientDone
403−
404−-- | Test typed channels
405−testTypedChannels :: NT.Transport -> Assertion
406−testTypedChannels transport = do
407− serverChannel <- newEmptyMVar :: IO (MVar (SendPort (SendPort Bool, Int)))
408− clientDone <- newEmptyMVar
409−
410− forkIO $ do
411− localNode <- newLocalNode transport initRemoteTable
412− forkProcess localNode $ do
413− (serverSendPort, rport) <- newChan
414− liftIO $ putMVar serverChannel serverSendPort
415− (clientSendPort, i) <- receiveChan rport
416− sendChan clientSendPort (even i)
417− return ()
418−
419− forkIO $ do
420− localNode <- newLocalNode transport initRemoteTable
421− serverSendPort <- readMVar serverChannel
422− runProcess localNode $ do
423− (clientSendPort, rport) <- newChan
424− sendChan serverSendPort (clientSendPort, 5)
425− False <- receiveChan rport
426− liftIO $ putMVar clientDone ()
427−
428− takeMVar clientDone
429−
430−-- | Test merging receive ports
431−testMergeChannels :: NT.Transport -> Assertion
432−testMergeChannels transport = do
433− localNode <- newLocalNode transport initRemoteTable
434− testFlat localNode True "aaabbbccc"
435− testFlat localNode False "abcabcabc"
436− testNested localNode True True "aaabbbcccdddeeefffggghhhiii"
437− testNested localNode True False "adgadgadgbehbehbehcficficfi"
438− testNested localNode False True "abcabcabcdefdefdefghighighi"
439− testNested localNode False False "adgbehcfiadgbehcfiadgbehcfi"
440− testBlocked localNode True
441− testBlocked localNode False
442− where
443− -- Single layer of merging
444− testFlat :: LocalNode -> Bool -> String -> IO ()
445− testFlat localNode biased expected = do
446− done <- newEmptyMVar
447− forkProcess localNode $ do
448− rs <- mapM charChannel "abc"
449− m <- mergePorts biased rs
450− xs <- replicateM 9 $ receiveChan m
451− True <- return $ xs == expected
452− liftIO $ putMVar done ()
453− takeMVar done
454−
455− -- Two layers of merging
456− testNested :: LocalNode -> Bool -> Bool -> String -> IO ()
457− testNested localNode biasedInner biasedOuter expected = do
458− done <- newEmptyMVar
459− forkProcess localNode $ do
460− rss <- mapM (mapM charChannel) ["abc", "def", "ghi"]
461− ms <- mapM (mergePorts biasedInner) rss
462− m <- mergePorts biasedOuter ms
463− xs <- replicateM (9 * 3) $ receiveChan m
464− True <- return $ xs == expected
465− liftIO $ putMVar done ()
466− takeMVar done
467−
468− -- Test that if no messages are (immediately) available, the scheduler makes no difference
469− testBlocked :: LocalNode -> Bool -> IO ()
470− testBlocked localNode biased = do
471− vs <- replicateM 3 newEmptyMVar
472− done <- newEmptyMVar
473−
474− forkProcess localNode $ do
475− [sa, sb, sc] <- liftIO $ mapM readMVar vs
476− mapM_ ((>> liftIO (threadDelay 10000)) . uncurry sendChan)
477− [ -- a, b, c
478− (sa, 'a')
479− , (sb, 'b')
480− , (sc, 'c')
481− -- a, c, b
482− , (sa, 'a')
483− , (sc, 'c')
484− , (sb, 'b')
485− -- b, a, c
486− , (sb, 'b')
487− , (sa, 'a')
488− , (sc, 'c')
489− -- b, c, a
490− , (sb, 'b')
491− , (sc, 'c')
492− , (sa, 'a')
493− -- c, a, b
494− , (sc, 'c')
495− , (sa, 'a')
496− , (sb, 'b')
497− -- c, b, a
498− , (sc, 'c')
499− , (sb, 'b')
500− , (sa, 'a')
501− ]
502−
503− forkProcess localNode $ do
504− (ss, rs) <- unzip <$> replicateM 3 newChan
505− liftIO $ mapM_ (uncurry putMVar) $ zip vs ss
506− m <- mergePorts biased rs
507− xs <- replicateM (6 * 3) $ receiveChan m
508− True <- return $ xs == "abcacbbacbcacabcba"
509− liftIO $ putMVar done ()
510−
511− takeMVar done
512−
513− mergePorts :: Serializable a => Bool -> [ReceivePort a] -> Process (ReceivePort a)
514− mergePorts True = mergePortsBiased
515− mergePorts False = mergePortsRR
516−
517− charChannel :: Char -> Process (ReceivePort Char)
518− charChannel c = do
519− (sport, rport) <- newChan
520− replicateM_ 3 $ sendChan sport c
521− liftIO $ threadDelay 10000 -- Make sure messages have been sent
522− return rport
523−
524−testTerminate :: NT.Transport -> Assertion
525−testTerminate transport = do
526− localNode <- newLocalNode transport initRemoteTable
527− done <- newEmptyMVar
528−
529− pid <- forkProcess localNode $ do
530− liftIO $ threadDelay 100000
531− terminate
532−
533− runProcess localNode $ do
534− ref <- monitor pid
535− ProcessMonitorNotification ref' pid' (DiedException ex) <- expect
536− True <- return $ ref == ref' && pid == pid' && ex == show ProcessTerminationException
537− liftIO $ putMVar done ()
538−
539− takeMVar done
540−
541−testMonitorNode :: NT.Transport -> Assertion
542−testMonitorNode transport = do
543− [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
544− done <- newEmptyMVar
545−
546− closeLocalNode node1
547−
548− runProcess node2 $ do
549− ref <- monitorNode (localNodeId node1)
550− NodeMonitorNotification ref' nid DiedDisconnect <- expect
551− True <- return $ ref == ref' && nid == localNodeId node1
552− liftIO $ putMVar done ()
553−
554− takeMVar done
555−
556−testMonitorChannel :: NT.Transport -> Assertion
557−testMonitorChannel transport = do
558− [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
559− gotNotification <- newEmptyMVar
560−
561− pid <- forkProcess node1 $ do
562− sport <- expect :: Process (SendPort ())
563− ref <- monitorPort sport
564− PortMonitorNotification ref' port' reason <- expect
565− -- reason might be DiedUnknownId if the receive port is GCed before the
566− -- monitor is established (TODO: not sure that this is reasonable)
567− return $ ref' == ref && port' == sendPortId sport && (reason == DiedNormal || reason == DiedUnknownId)
568− liftIO $ putMVar gotNotification ()
569−
570− runProcess node2 $ do
571− (sport, _) <- newChan :: Process (SendPort (), ReceivePort ())
572− send pid sport
573− liftIO $ threadDelay 100000
574−
575− takeMVar gotNotification
576−
577−testRegistry :: NT.Transport -> Assertion
578−testRegistry transport = do
579− node <- newLocalNode transport initRemoteTable
580− done <- newEmptyMVar
581−
582− pingServer <- forkProcess node ping
583−
584− runProcess node $ do
585− register "ping" pingServer
586− Just pid <- whereis "ping"
587− True <- return $ pingServer == pid
588− us <- getSelfPid
589− nsend "ping" (Pong us)
590− Ping pid' <- expect
591− True <- return $ pingServer == pid'
592− liftIO $ putMVar done ()
593−
594− takeMVar done
595−
596−testRemoteRegistry :: NT.Transport -> Assertion
597−testRemoteRegistry transport = do
598− node1 <- newLocalNode transport initRemoteTable
599− node2 <- newLocalNode transport initRemoteTable
600− done <- newEmptyMVar
601−
602− pingServer <- forkProcess node1 ping
603−
604− runProcess node2 $ do
605− let nid1 = localNodeId node1
606− registerRemoteAsync nid1 "ping" pingServer
607− receiveWait [
608− matchIf (\(RegisterReply label' _) -> "ping" == label')
609− (\(RegisterReply _ _) -> return ()) ]
610−
611− Just pid <- whereisRemote nid1 "ping"
612− True <- return $ pingServer == pid
613− us <- getSelfPid
614− nsendRemote nid1 "ping" (Pong us)
615− Ping pid' <- expect
616− True <- return $ pingServer == pid'
617− liftIO $ putMVar done ()
618−
619− takeMVar done
620−
621−testSpawnLocal :: NT.Transport -> Assertion
622−testSpawnLocal transport = do
623− node <- newLocalNode transport initRemoteTable
624− done <- newEmptyMVar
625−
626− runProcess node $ do
627− us <- getSelfPid
628−
629− pid <- spawnLocal $ do
630− sport <- expect
631− sendChan sport (1234 :: Int)
632−
633− sport <- spawnChannelLocal $ \rport -> do
634− (1234 :: Int) <- receiveChan rport
635− send us ()
636−
637− send pid sport
638− () <- expect
639− liftIO $ putMVar done ()
640−
641− takeMVar done
642−
643−testReconnect :: NT.Transport -> TransportInternals -> Assertion
644−testReconnect transport transportInternals = do
645− [node1, node2] <- replicateM 2 $ newLocalNode transport initRemoteTable
646− let nid1 = localNodeId node1
647− nid2 = localNodeId node2
648− processA <- newEmptyMVar
649− [sendTestOk, registerTestOk] <- replicateM 2 newEmptyMVar
650−
651− forkProcess node1 $ do
652− us <- getSelfPid
653− liftIO $ putMVar processA us
654− msg1 <- expect
655− msg2 <- expect
656− True <- return $ msg1 == "message 1" && msg2 == "message 3"
657− liftIO $ putMVar sendTestOk ()
658−
659− forkProcess node2 $ do
660− {-
661− - Make sure there is no implicit reconnect on normal message sending
662− -}
663−
664− them <- liftIO $ readMVar processA
665− send them "message 1" >> liftIO (threadDelay 100000)
666−
667− -- Simulate network failure
668− liftIO $ do
669− sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)
670− sClose sock
671− threadDelay 10000
672−
673− -- Should not arrive
674− send them "message 2"
675−
676− -- Should arrive
677− reconnect them
678− send them "message 3"
679−
680− liftIO $ takeMVar sendTestOk
681−
682− {-
683− - Test that there *is* implicit reconnect on node controller messages
684− -}
685−
686− us <- getSelfPid
687− registerRemoteAsync nid1 "a" us -- registerRemote is asynchronous
688− receiveWait [
689− matchIf (\(RegisterReply label' _) -> "a" == label')
690− (\(RegisterReply _ _) -> return ()) ]
691−
692− Just _ <- whereisRemote nid1 "a"
693−
694−
695− -- Simulate network failure
696− liftIO $ do
697− sock <- socketBetween transportInternals (nodeAddress nid1) (nodeAddress nid2)
698− sClose sock
699− threadDelay 10000
700−
701− -- This will happen due to implicit reconnect
702− registerRemoteAsync nid1 "b" us
703− receiveWait [
704− matchIf (\(RegisterReply label' _) -> "b" == label')
705− (\(RegisterReply _ _) -> return ()) ]
706−
707− -- Should happen
708− registerRemoteAsync nid1 "c" us
709− receiveWait [
710− matchIf (\(RegisterReply label' _) -> "c" == label')
711− (\(RegisterReply _ _) -> return ()) ]
712−
713− -- Check
714− Nothing <- whereisRemote nid1 "a" -- this will fail because the name is removed when the node is disconnected
715− Just _ <- whereisRemote nid1 "b" -- this will suceed because the value is set after thereconnect
716− Just _ <- whereisRemote nid1 "c"
717−
718− liftIO $ putMVar registerTestOk ()
719−
720− takeMVar registerTestOk
721−
722−-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
723−-- in between
724−testMatchAny :: NT.Transport -> Assertion
725−testMatchAny transport = do
726− proxyAddr <- newEmptyMVar
727− clientDone <- newEmptyMVar
728−
729− -- Math server
730− forkIO $ do
731− localNode <- newLocalNode transport initRemoteTable
732− mathServer <- forkProcess localNode math
733− proxyServer <- forkProcess localNode $ forever $ do
734− msg <- receiveWait [ matchAny return ]
735− forward msg mathServer
736− putMVar proxyAddr proxyServer
737−
738− -- Client
739− forkIO $ do
740− localNode <- newLocalNode transport initRemoteTable
741− mathServer <- readMVar proxyAddr
742−
743− runProcess localNode $ do
744− pid <- getSelfPid
745− send mathServer (Add pid 1 2)
746− 3 <- expect :: Process Double
747− send mathServer (Divide pid 8 2)
748− 4 <- expect :: Process Double
749− send mathServer (Divide pid 8 0)
750− DivByZero <- expect
751− liftIO $ putMVar clientDone ()
752−
753− takeMVar clientDone
754−
755−-- | Test 'matchAny'. This repeats the 'testMath' but with a proxy server
756−-- in between, however we block 'Divide' requests ....
757−testMatchAnyHandle :: NT.Transport -> Assertion
758−testMatchAnyHandle transport = do
759− proxyAddr <- newEmptyMVar
760− clientDone <- newEmptyMVar
761−
762− -- Math server
763− forkIO $ do
764− localNode <- newLocalNode transport initRemoteTable
765− mathServer <- forkProcess localNode math
766− proxyServer <- forkProcess localNode $ forever $ do
767− receiveWait [
768− matchAny (maybeForward mathServer)
769− ]
770− putMVar proxyAddr proxyServer
771−
772− -- Client
773− forkIO $ do
774− localNode <- newLocalNode transport initRemoteTable
775− mathServer <- readMVar proxyAddr
776−
777− runProcess localNode $ do
778− pid <- getSelfPid
779− send mathServer (Add pid 1 2)
780− 3 <- expect :: Process Double
781− send mathServer (Divide pid 8 2)
782− Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
783− liftIO $ putMVar clientDone ()
784−
785− takeMVar clientDone
786− where maybeForward :: ProcessId -> Message -> Process (Maybe ())
787− maybeForward s msg =
788− handleMessage msg (\m@(Add _ _ _) -> send s m)
789−
790−testMatchAnyNoHandle :: NT.Transport -> Assertion
791−testMatchAnyNoHandle transport = do
792− addr <- newEmptyMVar
793− clientDone <- newEmptyMVar
794− serverDone <- newEmptyMVar
795−
796− -- Math server
797− forkIO $ do
798− localNode <- newLocalNode transport initRemoteTable
799− server <- forkProcess localNode $ forever $ do
800− receiveWait [
801− matchAnyIf
802− -- the condition has type `Add -> Bool`
803− (\(Add _ _ _) -> True)
804− -- the match `AbstractMessage -> Process ()` will succeed!
805− (\m -> do
806− -- `String -> Process ()` does *not* match the input types however
807− r <- (handleMessage m (\(_ :: String) -> die "NONSENSE" ))
808− case r of
809− Nothing -> return ()
810− Just _ -> die "NONSENSE")
811− ]
812− -- we *must* have removed the message from our mailbox though!!!
813− Nothing <- receiveTimeout 100000 [ match (\(Add _ _ _) -> return ()) ]
814− liftIO $ putMVar serverDone ()
815− putMVar addr server
816−
817− -- Client
818− forkIO $ do
819− localNode <- newLocalNode transport initRemoteTable
820− server <- readMVar addr
821−
822− runProcess localNode $ do
823− pid <- getSelfPid
824− send server (Add pid 1 2)
825− -- we only care about the client having sent a message, so we're done
826− liftIO $ putMVar clientDone ()
827−
828− takeMVar clientDone
829− takeMVar serverDone
830−
831−-- | Test 'matchAnyIf'. We provide an /echo/ server, but it ignores requests
832−-- unless the text body @/= "bar"@ - this case should time out rather than
833−-- removing the message from the process mailbox.
834−testMatchAnyIf :: NT.Transport -> Assertion
835−testMatchAnyIf transport = do
836− echoAddr <- newEmptyMVar
837− clientDone <- newEmptyMVar
838−
839− -- echo server
840− forkIO $ do
841− localNode <- newLocalNode transport initRemoteTable
842− echoServer <- forkProcess localNode $ forever $ do
843− receiveWait [
844− matchAnyIf (\(_ :: ProcessId, (s :: String)) -> s /= "bar")
845− tryHandleMessage
846− ]
847− putMVar echoAddr echoServer
848−
849− -- Client
850− forkIO $ do
851− localNode <- newLocalNode transport initRemoteTable
852− server <- readMVar echoAddr
853−
854− runProcess localNode $ do
855− pid <- getSelfPid
856− send server (pid, "foo")
857− "foo" <- expect
858− send server (pid, "baz")
859− "baz" <- expect
860− send server (pid, "bar")
861− Nothing <- (expectTimeout 100000) :: Process (Maybe Double)
862− liftIO $ putMVar clientDone ()
863−
864− takeMVar clientDone
865− where tryHandleMessage :: Message -> Process (Maybe ())
866− tryHandleMessage msg =
867− handleMessage msg (\(pid :: ProcessId, (m :: String))
868− -> do { send pid m; return () })
869−
870−testMatchMessageWithUnwrap :: NT.Transport -> Assertion
871−testMatchMessageWithUnwrap transport = do
872− echoAddr <- newEmptyMVar
873− clientDone <- newEmptyMVar
874−
875− -- echo server
876− forkIO $ do
877− localNode <- newLocalNode transport initRemoteTable
878− echoServer <- forkProcess localNode $ forever $ do
879− msg <- receiveWait [
880− matchMessage (\(m :: Message) -> do
881− return m)
882− ]
883− unwrapped <- unwrapMessage msg :: Process (Maybe (ProcessId, Message))
884− case unwrapped of
885− (Just (p, msg')) -> forward msg' p
886− Nothing -> die "unable to unwrap the message"
887− putMVar echoAddr echoServer
888−
889− -- Client
890− forkIO $ do
891− localNode <- newLocalNode transport initRemoteTable
892− server <- readMVar echoAddr
893−
894− runProcess localNode $ do
895− pid <- getSelfPid
896− send server (pid, wrapMessage ("foo" :: String))
897− "foo" <- expect
898− send server (pid, wrapMessage ("baz" :: String))
899− "baz" <- expect
900− liftIO $ putMVar clientDone ()
901−
902− takeMVar clientDone
903−
904−-- Test 'receiveChanTimeout'
905−testReceiveChanTimeout :: NT.Transport -> Assertion
906−testReceiveChanTimeout transport = do
907− done <- newEmptyMVar
908− sendPort <- newEmptyMVar
909−
910− forkTry $ do
911− localNode <- newLocalNode transport initRemoteTable
912− runProcess localNode $ do
913− -- Create a typed channel
914− (sp, rp) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
915− liftIO $ putMVar sendPort sp
916−
917− -- Wait for a message with a delay. No message arrives, we should get Nothing after 1 second
918− Nothing <- receiveChanTimeout 1000000 rp
919−
920− -- Wait for a message with a delay again. Now a message arrives after 0.5 seconds
921− Just True <- receiveChanTimeout 1000000 rp
922−
923− -- Wait for a message with zero timeout: non-blocking check. No message is available, we get Nothing
924− Nothing <- receiveChanTimeout 0 rp
925−
926− -- Again, but now there is a message available
927− liftIO $ threadDelay 1000000
928− Just False <- receiveChanTimeout 0 rp
929−
930− liftIO $ putMVar done ()
931−
932− forkTry $ do
933− localNode <- newLocalNode transport initRemoteTable
934− runProcess localNode $ do
935− sp <- liftIO $ readMVar sendPort
936−
937− liftIO $ threadDelay 1500000
938− sendChan sp True
939−
940− liftIO $ threadDelay 500000
941− sendChan sp False
942−
943− takeMVar done
944−
945−-- | Test Functor, Applicative, Alternative and Monad instances for ReceiveChan
946−testReceiveChanFeatures :: NT.Transport -> Assertion
947−testReceiveChanFeatures transport = do
948− done <- newEmptyMVar
949−
950− forkTry $ do
951− localNode <- newLocalNode transport initRemoteTable
952− runProcess localNode $ do
953− (spInt, rpInt) <- newChan :: Process (SendPort Int, ReceivePort Int)
954− (spBool, rpBool) <- newChan :: Process (SendPort Bool, ReceivePort Bool)
955−
956− -- Test Functor instance
957−
958− sendChan spInt 2
959− sendChan spBool False
960−
961− rp1 <- mergePortsBiased [even <$> rpInt, rpBool]
962−
963− True <- receiveChan rp1
964− False <- receiveChan rp1
965−
966− -- Test Applicative instance
967−
968− sendChan spInt 3
969− sendChan spInt 4
970−
971− let rp2 = pure (+) <*> rpInt <*> rpInt
972−
973− 7 <- receiveChan rp2
974−
975− -- Test Alternative instance
976−
977− sendChan spInt 3
978− sendChan spBool True
979−
980− let rp3 = (even <$> rpInt) <|> rpBool
981−
982− False <- receiveChan rp3
983− True <- receiveChan rp3
984−
985− -- Test Monad instance
986−
987− sendChan spBool True
988− sendChan spBool False
989− sendChan spInt 5
990−
991− let rp4 :: ReceivePort Int
992− rp4 = do b <- rpBool
993− if b
994− then rpInt
995− else return 7
996−
997− 5 <- receiveChan rp4
998− 7 <- receiveChan rp4
999−
1000− liftIO $ putMVar done ()
1001−
1002− takeMVar done
1003−
1004−testKillLocal :: NT.Transport -> Assertion
1005−testKillLocal transport = do
1006− localNode <- newLocalNode transport initRemoteTable
1007− done <- newEmptyMVar
1008−
1009− pid <- forkProcess localNode $ do
1010− liftIO $ threadDelay 1000000
1011−
1012− runProcess localNode $ do
1013− ref <- monitor pid
1014− us <- getSelfPid
1015− kill pid "TestKill"
1016− ProcessMonitorNotification ref' pid' (DiedException ex) <- expect
1017− True <- return $ ref == ref' && pid == pid' && ex == "killed-by=" ++ show us ++ ",reason=TestKill"
1018− liftIO $ putMVar done ()
1019−
1020− takeMVar done
1021−
1022−testKillRemote :: NT.Transport -> Assertion
1023−testKillRemote transport = do
1024− node1 <- newLocalNode transport initRemoteTable
1025− node2 <- newLocalNode transport initRemoteTable
1026− done <- newEmptyMVar
1027−
1028− pid <- forkProcess node1 $ do
1029− liftIO $ threadDelay 1000000
1030−
1031− runProcess node2 $ do
1032− ref <- monitor pid
1033− us <- getSelfPid
1034− kill pid "TestKill"
1035− ProcessMonitorNotification ref' pid' (DiedException reason) <- expect
1036− True <- return $ ref == ref' && pid == pid' && reason == "killed-by=" ++ show us ++ ",reason=TestKill"
1037− liftIO $ putMVar done ()
1038−
1039− takeMVar done
1040−
1041−testCatchesExit :: NT.Transport -> Assertion
1042−testCatchesExit transport = do
1043− localNode <- newLocalNode transport initRemoteTable
1044− done <- newEmptyMVar
1045−
1046− _ <- forkProcess localNode $ do
1047− (die ("foobar", 123 :: Int))
1048− `catchesExit` [
1049− (\_ m -> handleMessage m (\(_ :: String) -> return ()))
1050− , (\_ m -> handleMessage m (\(_ :: Maybe Int) -> return ()))
1051− , (\_ m -> handleMessage m (\(_ :: String, _ :: Int)
1052− -> (liftIO $ putMVar done ()) >> return ()))
1053− ]
1054−
1055− takeMVar done
1056−
1057−testCatches :: NT.Transport -> Assertion
1058−testCatches transport = do
1059− localNode <- newLocalNode transport initRemoteTable
1060− done <- newEmptyMVar
1061−
1062− _ <- forkProcess localNode $ do
1063− node <- getSelfNode
1064− (liftIO $ throwIO (ProcessLinkException (nullProcessId node) DiedNormal))
1065− `catches` [
1066− Handler (\(ProcessLinkException _ _) -> liftIO $ putMVar done ())
1067− ]
1068−
1069− takeMVar done
1070−
1071−testDie :: NT.Transport -> Assertion
1072−testDie transport = do
1073− localNode <- newLocalNode transport initRemoteTable
1074− done <- newEmptyMVar
1075−
1076− _ <- forkProcess localNode $ do
1077− (die ("foobar", 123 :: Int))
1078− `catchExit` \_from reason -> do
1079− -- TODO: should verify that 'from' has the right value
1080− True <- return $ reason == ("foobar", 123 :: Int)
1081− liftIO $ putMVar done ()
1082−
1083− takeMVar done
1084−
1085−testPrettyExit :: NT.Transport -> Assertion
1086−testPrettyExit transport = do
1087− localNode <- newLocalNode transport initRemoteTable
1088− done <- newEmptyMVar
1089−
1090− _ <- forkProcess localNode $ do
1091− (die "timeout")
1092− `catch` \(ex :: ProcessExitException) ->
1093− let expected = "exit-from=" ++ (show (exitSource ex))
1094− in do
1095− True <- return $ (show ex) == expected
1096− liftIO $ putMVar done ()
1097−
1098− takeMVar done
1099−
1100−testExitLocal :: NT.Transport -> Assertion
1101−testExitLocal transport = do
1102− localNode <- newLocalNode transport initRemoteTable
1103− supervisedDone <- newEmptyMVar
1104− supervisorDone <- newEmptyMVar
1105−
1106− pid <- forkProcess localNode $ do
1107− (liftIO $ threadDelay 100000)
1108− `catchExit` \_from reason -> do
1109− -- TODO: should verify that 'from' has the right value
1110− True <- return $ reason == "TestExit"
1111− liftIO $ putMVar supervisedDone ()
1112−
1113− runProcess localNode $ do
1114− ref <- monitor pid
1115− exit pid "TestExit"
1116− -- This time the client catches the exception, so it dies normally
1117− ProcessMonitorNotification ref' pid' DiedNormal <- expect
1118− True <- return $ ref == ref' && pid == pid'
1119− liftIO $ putMVar supervisorDone ()
1120−
1121− takeMVar supervisedDone
1122− takeMVar supervisorDone
1123−
1124−testExitRemote :: NT.Transport -> Assertion
1125−testExitRemote transport = do
1126− node1 <- newLocalNode transport initRemoteTable
1127− node2 <- newLocalNode transport initRemoteTable
1128− supervisedDone <- newEmptyMVar
1129− supervisorDone <- newEmptyMVar
1130−
1131− pid <- forkProcess node1 $ do
1132− (liftIO $ threadDelay 100000)
1133− `catchExit` \_from reason -> do
1134− -- TODO: should verify that 'from' has the right value
1135− True <- return $ reason == "TestExit"
1136− liftIO $ putMVar supervisedDone ()
1137−
1138− runProcess node2 $ do
1139− ref <- monitor pid
1140− exit pid "TestExit"
1141− ProcessMonitorNotification ref' pid' DiedNormal <- expect
1142− True <- return $ ref == ref' && pid == pid'
1143− liftIO $ putMVar supervisorDone ()
1144−
1145− takeMVar supervisedDone
1146− takeMVar supervisorDone
1147−
1148−tests :: (NT.Transport, TransportInternals) -> [Test]
1149−tests (transport, transportInternals) = [
1150− testGroup "Basic features" [
1151− testCase "Ping" (testPing transport)
1152− , testCase "Math" (testMath transport)
1153− , testCase "Timeout" (testTimeout transport)
1154− , testCase "Timeout0" (testTimeout0 transport)
1155− , testCase "SendToTerminated" (testSendToTerminated transport)
1156− , testCase "TypedChannnels" (testTypedChannels transport)
1157− , testCase "MergeChannels" (testMergeChannels transport)
1158− , testCase "Terminate" (testTerminate transport)
1159− , testCase "Registry" (testRegistry transport)
1160− , testCase "RemoteRegistry" (testRemoteRegistry transport)
1161− , testCase "SpawnLocal" (testSpawnLocal transport)
1162− , testCase "MatchAny" (testMatchAny transport)
1163− , testCase "MatchAnyHandle" (testMatchAnyHandle transport)
1164− , testCase "MatchAnyNoHandle" (testMatchAnyNoHandle transport)
1165− , testCase "MatchAnyIf" (testMatchAnyIf transport)
1166− , testCase "MatchMessageUnwrap" (testMatchMessageWithUnwrap transport)
1167− , testCase "ReceiveChanTimeout" (testReceiveChanTimeout transport)
1168− , testCase "ReceiveChanFeatures" (testReceiveChanFeatures transport)
1169− , testCase "KillLocal" (testKillLocal transport)
1170− , testCase "KillRemote" (testKillRemote transport)
1171− , testCase "Die" (testDie transport)
1172− , testCase "PrettyExit" (testPrettyExit transport)
1173− , testCase "CatchesExit" (testCatchesExit transport)
1174− , testCase "Catches" (testCatches transport)
1175− , testCase "ExitLocal" (testExitLocal transport)
1176− , testCase "ExitRemote" (testExitRemote transport)
1177− ]
1178− , testGroup "Monitoring and Linking" [
1179− -- Monitoring processes
1180− --
1181− -- The "missing" combinations in the list below don't make much sense, as
1182− -- we cannot guarantee that the monitor reply or link exception will not
1183− -- happen before the unmonitor or unlink
1184− testCase "MonitorUnreachable" (testMonitorUnreachable transport True False)
1185− , testCase "MonitorNormalTermination" (testMonitorNormalTermination transport True False)
1186− , testCase "MonitorAbnormalTermination" (testMonitorAbnormalTermination transport True False)
1187− , testCase "MonitorLocalDeadProcess" (testMonitorLocalDeadProcess transport True False)
1188− , testCase "MonitorRemoteDeadProcess" (testMonitorRemoteDeadProcess transport True False)
1189− , testCase "MonitorDisconnect" (testMonitorDisconnect transport True False)
1190− , testCase "LinkUnreachable" (testMonitorUnreachable transport False False)
1191− , testCase "LinkNormalTermination" (testMonitorNormalTermination transport False False)
1192− , testCase "LinkAbnormalTermination" (testMonitorAbnormalTermination transport False False)
1193− , testCase "LinkLocalDeadProcess" (testMonitorLocalDeadProcess transport False False)
1194− , testCase "LinkRemoteDeadProcess" (testMonitorRemoteDeadProcess transport False False)
1195− , testCase "LinkDisconnect" (testMonitorDisconnect transport False False)
1196− , testCase "UnmonitorNormalTermination" (testMonitorNormalTermination transport True True)
1197− , testCase "UnmonitorAbnormalTermination" (testMonitorAbnormalTermination transport True True)
1198− , testCase "UnmonitorDisconnect" (testMonitorDisconnect transport True True)
1199− , testCase "UnlinkNormalTermination" (testMonitorNormalTermination transport False True)
1200− , testCase "UnlinkAbnormalTermination" (testMonitorAbnormalTermination transport False True)
1201− , testCase "UnlinkDisconnect" (testMonitorDisconnect transport False True)
1202− -- Monitoring nodes and channels
1203− , testCase "MonitorNode" (testMonitorNode transport)
1204− , testCase "MonitorChannel" (testMonitorChannel transport)
1205− -- Reconnect
1206− , testCase "Reconnect" (testReconnect transport transportInternals)
1207− ]
1208− ]
1209−
1210−main :: IO ()
1211−main = do
1212− Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
1213− defaultMain (tests transport)
tests/TestClosure.hsdeleted−479
@@ -1,479 +0,0 @@
1−{-# LANGUAGE TemplateHaskell #-}
2−module Main where
3−
4−import Data.ByteString.Lazy (empty)
5−import Data.Typeable (Typeable)
6−import Control.Monad (join, replicateM, forever, replicateM_, void)
7−import Control.Exception (IOException, throw)
8−import Control.Concurrent (forkIO, threadDelay)
9−import Control.Concurrent.MVar
10− ( MVar
11− , newEmptyMVar
12− , readMVar
13− , takeMVar
14− , putMVar
15− , modifyMVar_
16− , newMVar
17− )
18−import Control.Applicative ((<$>))
19−import System.Random (randomIO)
20−import Network.Transport (Transport)
21−import Network.Transport.TCP
22− ( createTransportExposeInternals
23− , defaultTCPParameters
24− , TransportInternals(socketBetween)
25− )
26−import Network.Socket (sClose)
27−import Control.Distributed.Process
28−import Control.Distributed.Process.Closure
29−import Control.Distributed.Process.Node
30−import Control.Distributed.Static (staticLabel, staticClosure)
31−
32−import Test.HUnit (Assertion)
33−import Test.Framework (Test, defaultMain)
34−import Test.Framework.Providers.HUnit (testCase)
35−
36−--------------------------------------------------------------------------------
37−-- Supporting definitions --
38−--------------------------------------------------------------------------------
39−
40−quintuple :: a -> b -> c -> d -> e -> (a, b, c, d, e)
41−quintuple a b c d e = (a, b, c, d, e)
42−
43−sdictInt :: SerializableDict Int
44−sdictInt = SerializableDict
45−
46−factorial :: Int -> Process Int
47−factorial 0 = return 1
48−factorial n = (n *) <$> factorial (n - 1)
49−
50−addInt :: Int -> Int -> Int
51−addInt x y = x + y
52−
53−putInt :: Int -> MVar Int -> IO ()
54−putInt = flip putMVar
55−
56−sendPid :: ProcessId -> Process ()
57−sendPid toPid = do
58− fromPid <- getSelfPid
59− send toPid fromPid
60−
61−wait :: Int -> Process ()
62−wait = liftIO . threadDelay
63−
64−isPrime :: Integer -> Process Bool
65−isPrime n = return . (n `elem`) . takeWhile (<= n) . sieve $ [2..]
66− where
67− sieve :: [Integer] -> [Integer]
68− sieve (p : xs) = p : sieve [x | x <- xs, x `mod` p > 0]
69− sieve [] = error "Uh oh -- we've run out of primes"
70−
71−-- | First argument indicates empty closure environment
72−typedPingServer :: () -> ReceivePort (SendPort ()) -> Process ()
73−typedPingServer () rport = forever $ do
74− sport <- receiveChan rport
75− sendChan sport ()
76−
77−signal :: ProcessId -> Process ()
78−signal pid = send pid ()
79−
80−remotable [ 'factorial
81− , 'addInt
82− , 'putInt
83− , 'sendPid
84− , 'sdictInt
85− , 'wait
86− , 'typedPingServer
87− , 'isPrime
88− , 'quintuple
89− , 'signal
90− ]
91−
92−randomElement :: [a] -> IO a
93−randomElement xs = do
94− ix <- randomIO
95− return (xs !! (ix `mod` length xs))
96−
97−remotableDecl [
98− [d| dfib :: ([NodeId], SendPort Integer, Integer) -> Process () ;
99− dfib (_, reply, 0) = sendChan reply 0
100− dfib (_, reply, 1) = sendChan reply 1
101− dfib (nids, reply, n) = do
102− nid1 <- liftIO $ randomElement nids
103− nid2 <- liftIO $ randomElement nids
104− (sport, rport) <- newChan
105− spawn nid1 $ $(mkClosure 'dfib) (nids, sport, n - 2)
106− spawn nid2 $ $(mkClosure 'dfib) (nids, sport, n - 1)
107− n1 <- receiveChan rport
108− n2 <- receiveChan rport
109− sendChan reply $ n1 + n2
110− |]
111− ]
112−
113−-- Just try creating a static polymorphic value
114−staticQuintuple :: (Typeable a, Typeable b, Typeable c, Typeable d, Typeable e)
115− => Static (a -> b -> c -> d -> e -> (a, b, c, d, e))
116−staticQuintuple = $(mkStatic 'quintuple)
117−
118−factorialClosure :: Int -> Closure (Process Int)
119−factorialClosure = $(mkClosure 'factorial)
120−
121−addIntClosure :: Int -> Closure (Int -> Int)
122−addIntClosure = $(mkClosure 'addInt)
123−
124−putIntClosure :: Int -> Closure (MVar Int -> IO ())
125−putIntClosure = $(mkClosure 'putInt)
126−
127−sendPidClosure :: ProcessId -> Closure (Process ())
128−sendPidClosure = $(mkClosure 'sendPid)
129−
130−sendFac :: Int -> ProcessId -> Closure (Process ())
131−sendFac n pid = factorialClosure n `bindCP` cpSend $(mkStatic 'sdictInt) pid
132−
133−factorialOf :: Closure (Int -> Process Int)
134−factorialOf = staticClosure $(mkStatic 'factorial)
135−
136−factorial' :: Int -> Closure (Process Int)
137−factorial' n = returnCP $(mkStatic 'sdictInt) n `bindCP` factorialOf
138−
139−waitClosure :: Int -> Closure (Process ())
140−waitClosure = $(mkClosure 'wait)
141−
142−simulateNetworkFailure :: TransportInternals -> NodeId -> NodeId -> Process ()
143−simulateNetworkFailure transportInternals fr to = liftIO $ do
144− threadDelay 10000
145− sock <- socketBetween transportInternals (nodeAddress fr) (nodeAddress to)
146− sClose sock
147− threadDelay 10000
148−
149−--------------------------------------------------------------------------------
150−-- The tests proper --
151−--------------------------------------------------------------------------------
152−
153−testUnclosure :: Transport -> RemoteTable -> Assertion
154−testUnclosure transport rtable = do
155− node <- newLocalNode transport rtable
156− done <- newEmptyMVar
157− forkProcess node $ do
158− 120 <- join . unClosure $ factorialClosure 5
159− liftIO $ putMVar done ()
160− takeMVar done
161−
162−testBind :: Transport -> RemoteTable -> Assertion
163−testBind transport rtable = do
164− node <- newLocalNode transport rtable
165− done <- newEmptyMVar
166− runProcess node $ do
167− us <- getSelfPid
168− join . unClosure $ sendFac 6 us
169− (720 :: Int) <- expect
170− liftIO $ putMVar done ()
171− takeMVar done
172−
173−testSendPureClosure :: Transport -> RemoteTable -> Assertion
174−testSendPureClosure transport rtable = do
175− serverAddr <- newEmptyMVar
176− serverDone <- newEmptyMVar
177−
178− forkIO $ do
179− node <- newLocalNode transport rtable
180− addr <- forkProcess node $ do
181− cl <- expect
182− fn <- unClosure cl :: Process (Int -> Int)
183− 13 <- return $ fn 6
184− liftIO $ putMVar serverDone ()
185− putMVar serverAddr addr
186−
187− forkIO $ do
188− node <- newLocalNode transport rtable
189− theirAddr <- readMVar serverAddr
190− runProcess node $ send theirAddr (addIntClosure 7)
191−
192− takeMVar serverDone
193−
194−testSendIOClosure :: Transport -> RemoteTable -> Assertion
195−testSendIOClosure transport rtable = do
196− serverAddr <- newEmptyMVar
197− serverDone <- newEmptyMVar
198−
199− forkIO $ do
200− node <- newLocalNode transport rtable
201− addr <- forkProcess node $ do
202− cl <- expect
203− io <- unClosure cl :: Process (MVar Int -> IO ())
204− liftIO $ do
205− someMVar <- newEmptyMVar
206− io someMVar
207− 5 <- readMVar someMVar
208− putMVar serverDone ()
209− putMVar serverAddr addr
210−
211− forkIO $ do
212− node <- newLocalNode transport rtable
213− theirAddr <- readMVar serverAddr
214− runProcess node $ send theirAddr (putIntClosure 5)
215−
216− takeMVar serverDone
217−
218−testSendProcClosure :: Transport -> RemoteTable -> Assertion
219−testSendProcClosure transport rtable = do
220− serverAddr <- newEmptyMVar
221− clientDone <- newEmptyMVar
222−
223− forkIO $ do
224− node <- newLocalNode transport rtable
225− addr <- forkProcess node $ do
226− cl <- expect
227− pr <- unClosure cl :: Process (Int -> Process ())
228− pr 5
229− putMVar serverAddr addr
230−
231− forkIO $ do
232− node <- newLocalNode transport rtable
233− theirAddr <- readMVar serverAddr
234− runProcess node $ do
235− pid <- getSelfPid
236− send theirAddr (cpSend $(mkStatic 'sdictInt) pid)
237− 5 <- expect :: Process Int
238− liftIO $ putMVar clientDone ()
239−
240− takeMVar clientDone
241−
242−testSpawn :: Transport -> RemoteTable -> Assertion
243−testSpawn transport rtable = do
244− serverNodeAddr <- newEmptyMVar
245− clientDone <- newEmptyMVar
246−
247− forkIO $ do
248− node <- newLocalNode transport rtable
249− putMVar serverNodeAddr (localNodeId node)
250−
251− forkIO $ do
252− node <- newLocalNode transport rtable
253− nid <- readMVar serverNodeAddr
254− runProcess node $ do
255− pid <- getSelfPid
256− pid' <- spawn nid (sendPidClosure pid)
257− pid'' <- expect
258− True <- return $ pid' == pid''
259− liftIO $ putMVar clientDone ()
260−
261− takeMVar clientDone
262−
263−testCall :: Transport -> RemoteTable -> Assertion
264−testCall transport rtable = do
265− serverNodeAddr <- newEmptyMVar
266− clientDone <- newEmptyMVar
267−
268− forkIO $ do
269− node <- newLocalNode transport rtable
270− putMVar serverNodeAddr (localNodeId node)
271−
272− forkIO $ do
273− node <- newLocalNode transport rtable
274− nid <- readMVar serverNodeAddr
275− runProcess node $ do
276− (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorialClosure 5)
277− liftIO $ putMVar clientDone ()
278−
279− takeMVar clientDone
280−
281−testCallBind :: Transport -> RemoteTable -> Assertion
282−testCallBind transport rtable = do
283− serverNodeAddr <- newEmptyMVar
284− clientDone <- newEmptyMVar
285−
286− forkIO $ do
287− node <- newLocalNode transport rtable
288− putMVar serverNodeAddr (localNodeId node)
289−
290− forkIO $ do
291− node <- newLocalNode transport rtable
292− nid <- readMVar serverNodeAddr
293− runProcess node $ do
294− (120 :: Int) <- call $(mkStatic 'sdictInt) nid (factorial' 5)
295− liftIO $ putMVar clientDone ()
296−
297− takeMVar clientDone
298−
299−testSeq :: Transport -> RemoteTable -> Assertion
300−testSeq transport rtable = do
301− node <- newLocalNode transport rtable
302− done <- newEmptyMVar
303− runProcess node $ do
304− us <- getSelfPid
305− join . unClosure $ sendFac 5 us `seqCP` sendFac 6 us
306− 120 :: Int <- expect
307− 720 :: Int <- expect
308− liftIO $ putMVar done ()
309− takeMVar done
310−
311−-- Test 'spawnSupervised'
312−--
313−-- Set up a supervisor, spawn a child, then have a third process monitor the
314−-- child. The supervisor then throws an exception, the child dies because it
315−-- was linked to the supervisor, and the third process notices that the child
316−-- dies.
317−testSpawnSupervised :: Transport -> RemoteTable -> Assertion
318−testSpawnSupervised transport rtable = do
319− [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
320− [superPid, childPid] <- replicateM 2 $ newEmptyMVar
321− thirdProcessDone <- newEmptyMVar
322−
323− forkProcess node1 $ do
324− us <- getSelfPid
325− liftIO $ putMVar superPid us
326− (child, _ref) <- spawnSupervised (localNodeId node2) (waitClosure 1000000)
327− liftIO $ do
328− putMVar childPid child
329− threadDelay 500000 -- Give the child a chance to link to us
330− throw supervisorDeath
331−
332− forkProcess node2 $ do
333− [super, child] <- liftIO $ mapM readMVar [superPid, childPid]
334− ref <- monitor child
335− ProcessMonitorNotification ref' pid' (DiedException e) <- expect
336− True <- return $ ref' == ref
337− && pid' == child
338− && e == show (ProcessLinkException super (DiedException (show supervisorDeath)))
339− liftIO $ putMVar thirdProcessDone ()
340−
341− takeMVar thirdProcessDone
342− where
343− supervisorDeath :: IOException
344− supervisorDeath = userError "Supervisor died"
345−
346−testSpawnInvalid :: Transport -> RemoteTable -> Assertion
347−testSpawnInvalid transport rtable = do
348− node <- newLocalNode transport rtable
349− done <- newEmptyMVar
350− forkProcess node $ do
351− (pid, ref) <- spawnMonitor (localNodeId node) (closure (staticLabel "ThisDoesNotExist") empty)
352− ProcessMonitorNotification ref' pid' _reason <- expect
353− -- Depending on the exact interleaving, reason might be NoProc or the exception thrown by the absence of the static closure
354− True <- return $ ref' == ref && pid == pid'
355− liftIO $ putMVar done ()
356− takeMVar done
357−
358−testClosureExpect :: Transport -> RemoteTable -> Assertion
359−testClosureExpect transport rtable = do
360− node <- newLocalNode transport rtable
361− done <- newEmptyMVar
362− runProcess node $ do
363− nodeId <- getSelfNode
364− us <- getSelfPid
365− them <- spawn nodeId $ cpExpect $(mkStatic 'sdictInt) `bindCP` cpSend $(mkStatic 'sdictInt) us
366− send them (1234 :: Int)
367− (1234 :: Int) <- expect
368− liftIO $ putMVar done ()
369− takeMVar done
370−
371−testSpawnChannel :: Transport -> RemoteTable -> Assertion
372−testSpawnChannel transport rtable = do
373− done <- newEmptyMVar
374− [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
375−
376− forkProcess node1 $ do
377− pingServer <- spawnChannel
378− (sdictSendPort sdictUnit)
379− (localNodeId node2)
380− ($(mkClosure 'typedPingServer) ())
381− (sendReply, receiveReply) <- newChan
382− sendChan pingServer sendReply
383− receiveChan receiveReply
384− liftIO $ putMVar done ()
385−
386− takeMVar done
387−
388−testTDict :: Transport -> RemoteTable -> Assertion
389−testTDict transport rtable = do
390− done <- newEmptyMVar
391− [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
392− forkProcess node1 $ do
393− True <- call $(functionTDict 'isPrime) (localNodeId node2) ($(mkClosure 'isPrime) (79 :: Integer))
394− liftIO $ putMVar done ()
395− takeMVar done
396−
397−testFib :: Transport -> RemoteTable -> Assertion
398−testFib transport rtable = do
399− nodes <- replicateM 4 $ newLocalNode transport rtable
400− done <- newEmptyMVar
401−
402− forkProcess (head nodes) $ do
403− (sport, rport) <- newChan
404− spawnLocal $ dfib (map localNodeId nodes, sport, 10)
405− 55 <- receiveChan rport :: Process Integer
406− liftIO $ putMVar done ()
407−
408− takeMVar done
409−
410−testSpawnReconnect :: Transport -> RemoteTable -> TransportInternals -> Assertion
411−testSpawnReconnect transport rtable transportInternals = do
412− [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
413− let nid1 = localNodeId node1
414− nid2 = localNodeId node2
415− done <- newEmptyMVar
416− iv <- newMVar (0 :: Int)
417−
418− incr <- forkProcess node1 $ forever $ do
419− () <- expect
420− liftIO $ modifyMVar_ iv (return . (+ 1))
421−
422− forkProcess node2 $ do
423− _pid1 <- spawn nid1 ($(mkClosure 'signal) incr)
424− simulateNetworkFailure transportInternals nid2 nid1
425− _pid2 <- spawn nid1 ($(mkClosure 'signal) incr)
426− _pid3 <- spawn nid1 ($(mkClosure 'signal) incr)
427−
428− liftIO $ threadDelay 100000
429−
430− count <- liftIO $ takeMVar iv
431− True <- return $ count == 2 || count == 3 -- It depends on which message we get first in 'spawn'
432−
433− liftIO $ putMVar done ()
434−
435− takeMVar done
436−
437−-- | 'spawn' used to ave a race condition which would be triggered if the
438−-- spawning process terminates immediately after spawning
439−testSpawnTerminate :: Transport -> RemoteTable -> Assertion
440−testSpawnTerminate transport rtable = do
441− slave <- newLocalNode transport rtable
442− master <- newLocalNode transport rtable
443− masterDone <- newEmptyMVar
444−
445− runProcess master $ do
446− us <- getSelfPid
447− replicateM_ 1000 . spawnLocal . void . spawn (localNodeId slave) $ $(mkClosure 'signal) us
448− replicateM_ 1000 $ (expect :: Process ())
449− liftIO $ putMVar masterDone ()
450−
451− takeMVar masterDone
452−
453−tests :: (Transport, TransportInternals) -> RemoteTable -> [Test]
454−tests (transport, transportInternals) rtable = [
455− testCase "Unclosure" (testUnclosure transport rtable)
456− , testCase "Bind" (testBind transport rtable)
457− , testCase "SendPureClosure" (testSendPureClosure transport rtable)
458− , testCase "SendIOClosure" (testSendIOClosure transport rtable)
459− , testCase "SendProcClosure" (testSendProcClosure transport rtable)
460− , testCase "Spawn" (testSpawn transport rtable)
461− , testCase "Call" (testCall transport rtable)
462− , testCase "CallBind" (testCallBind transport rtable)
463− , testCase "Seq" (testSeq transport rtable)
464− , testCase "SpawnSupervised" (testSpawnSupervised transport rtable)
465− , testCase "SpawnInvalid" (testSpawnInvalid transport rtable)
466− , testCase "ClosureExpect" (testClosureExpect transport rtable)
467− , testCase "SpawnChannel" (testSpawnChannel transport rtable)
468− , testCase "TDict" (testTDict transport rtable)
469− , testCase "Fib" (testFib transport rtable)
470− , testCase "SpawnTerminate" (testSpawnTerminate transport rtable)
471− , testCase "SpawnReconnect" (testSpawnReconnect transport rtable transportInternals)
472− ]
473−
474−
475−main :: IO ()
476−main = do
477− Right transport <- createTransportExposeInternals "127.0.0.1" "8080" defaultTCPParameters
478− let rtable = __remoteTable . __remoteTableDecl $ initRemoteTable
479− defaultMain (tests transport rtable)