git.iko.soy/distributed-process
on master help sign in
Put infrastructure in place for spawnChannel

It's not yet working though, not sure what's going on.

commit00ef03f358e3d0e8dd213fdbeb521ea4eb464c88 authorEdsko de Vries <edsko@edsko.net> dateThu 12 Jul 2012, 13:23 (14 years ago) parent3f40470
6 files changed+258−59
distributed-process/src/Control/Distributed/Process.hs+20−10 distributed-process/src/Control/Distributed/Process/Closure.hs+6 distributed-process/src/Control/Distributed/Process/Internal/Closure/CP.hs+140−48 distributed-process/src/Control/Distributed/Process/Internal/Closure/Static.hs+32 distributed-process/src/Control/Distributed/Process/Internal/Dynamic.hs+31 distributed-process/tests/TestClosure.hs+29−1
distributed-process/src/Control/Distributed/Process.hs+20−10
@@ -87,10 +87,12 @@ module Control.Distributed.Process
8787 , spawnSupervised
8888 , spawnLink
8989 , spawnMonitor
90+ , spawnChannel
9091 , DidSpawn(..)
9192 ) where
9293
9394import Prelude hiding (catch)
95+import Data.Typeable (Typeable)
9496import Control.Monad.IO.Class (liftIO)
9597import Control.Distributed.Process.Internal.Types
9698 ( RemoteTable
@@ -123,8 +125,14 @@ import Control.Distributed.Process.Internal.Closure.CP
123125 , cpExpect
124126 , cpLink
125127 , cpUnlink
128+ , cpNewChan
129+ , cpCancelL
130+ , cpSplit
131+ )
132+import Control.Distributed.Process.Internal.Closure.Static
133+ ( sdictUnit
134+ , sdictSendPort
126135 )
127−import Control.Distributed.Process.Internal.Closure.Static (sdictUnit)
128136import Control.Distributed.Process.Internal.Primitives
129137 ( -- Basic messaging
130138 send
@@ -316,16 +324,18 @@ spawnSupervised nid proc = do
316324 ref <- monitor them
317325 return (them, ref)
318326
319−{-
320−spawnChannel :: forall a. Typeable a
321− => NodeId
327+spawnChannel :: forall a. Typeable a => Static (SerializableDict a)
328+ -> NodeId
322329 -> Closure (ReceivePort a -> Process ())
323330 -> Process (SendPort a)
324−spawnChannel nid proc = do
331+spawnChannel dict nid proc = do
325332 us <- getSelfPid
326− undefined
333+ spawn nid (go us)
334+ expect
327335 where
328− rproc :: ProcessId -> Closure (Process ((), ReceivePort a))
329− rproc pid = newChanClosure
330− `cpBind` cpFirst (sendClosure undefined pid)
331−-}
336+ go :: ProcessId -> Closure (Process ())
337+ go pid = cpNewChan dict
338+ `cpBind`
339+ (cpSend (sdictSendPort dict) pid `cpSplit` proc)
340+ `cpBind`
341+ cpCancelL
distributed-process/src/Control/Distributed/Process/Closure.hs+6
@@ -102,6 +102,8 @@ module Control.Distributed.Process.Closure
102102 , staticFirst
103103 , staticSecond
104104 , staticSplit
105+ -- * Static constants
106+ , staticUnit
105107 -- * Creating closures
106108 , staticDecode
107109 , staticClosure
@@ -120,6 +122,7 @@ module Control.Distributed.Process.Closure
120122 , SerializableDict(..)
121123 , sdictUnit
122124 , sdictProcessId
125+ , sdictSendPort
123126 ) where
124127
125128import Control.Distributed.Process.Internal.Types
@@ -139,6 +142,8 @@ import Control.Distributed.Process.Internal.Closure.Static
139142 , staticFirst
140143 , staticSecond
141144 , staticSplit
145+ -- Constants
146+ , staticUnit
142147 -- Creating closures
143148 , staticDecode
144149 , staticClosure
@@ -146,6 +151,7 @@ import Control.Distributed.Process.Internal.Closure.Static
146151 -- Serialization dictionaries (and their static versions)
147152 , sdictUnit
148153 , sdictProcessId
154+ , sdictSendPort
149155 )
150156import Control.Distributed.Process.Internal.Closure.MkClosure (mkClosure)
151157import Control.Distributed.Process.Internal.Closure.CP
distributed-process/src/Control/Distributed/Process/Internal/Closure/CP.hs+140−48
@@ -1,8 +1,19 @@
11-- | Combinator for process closures
22{-# LANGUAGE MagicHash #-}
33module Control.Distributed.Process.Internal.Closure.CP
4− ( -- * Closure versions of CH primitives
5− cpLink
4+ ( -- * Definition of CP and the generalized arrow combinators
5+ CP
6+ , cpIntro
7+ , cpElim
8+ , cpId
9+ , cpComp
10+ , cpFirst
11+ , cpSecond
12+ , cpSplit
13+ , cpCancelL
14+ , cpCancelR
15+ -- * Closure versions of CH primitives
16+ , cpLink
617 , cpUnlink
718 , cpSend
819 , cpExpect
@@ -18,8 +29,9 @@ module Control.Distributed.Process.Internal.Closure.CP
1829import Data.Binary (encode)
1930import Data.ByteString.Lazy (ByteString)
2031import Data.Typeable (Typeable, typeOf, typeRepTyCon, TyCon)
32+import Data.Typeable.Internal (TypeRep(..))
2133import Control.Applicative ((<$>))
22−import Control.Monad (join)
34+import Control.Monad ((>=>))
2335import Control.Distributed.Process.Serializable (Serializable)
2436import Control.Distributed.Process.Internal.Types
2537 ( Closure(Closure)
@@ -53,14 +65,17 @@ import Control.Distributed.Process.Internal.Closure.Static
5365 , staticSplit
5466 , staticConst
5567 , staticUnit
68+ , staticFlip
69+ , staticFst
70+ , staticSnd
5671 , sdictProcessId
5772 )
5873import Control.Distributed.Process.Internal.Closure.MkClosure (mkClosure)
5974import Control.Distributed.Process.Internal.Dynamic
6075 ( Dynamic(Dynamic)
61− , dynBind
6276 , unsafeCoerce#
6377 , dynTypeRep
78+ , dynKleisli
6479 )
6580
6681--------------------------------------------------------------------------------
@@ -75,8 +90,11 @@ returnProcess = return
7590bindProcess :: Process a -> (a -> Process b) -> Process b
7691bindProcess = (>>=)
7792
78−joinProcess :: Process (Process a) -> Process a
79−joinProcess = join
93+kleisliProcess :: (a -> Process b) -> (b -> Process c) -> a -> Process c
94+kleisliProcess = (>=>)
95+
96+parJoinProcess :: Process (a -> Process b) -> a -> Process b
97+parJoinProcess proc a = proc >>= ($ a)
8098
8199---- Variations on standard or CH functions with an explicit dictionary arg ----
82100
@@ -92,8 +110,8 @@ newChanDict SerializableDict = newChan
92110---- Serialization dictionaries ------------------------------------------------
93111
94112-- | Specialized serialization dictionary required in 'cpBind'
95−sdictBind :: SerializableDict (ByteString, ByteString)
96−sdictBind = SerializableDict
113+sdictComp :: SerializableDict (ByteString, ByteString)
114+sdictComp = SerializableDict
97115
98116---- Some specialised processes necessary to implement the combinators ---------
99117
@@ -105,42 +123,54 @@ unClosure (Static label) env = do
105123 Nothing -> fail "Derived.unClosure: resolveClosure failed"
106124 Just dyn -> return dyn
107125
126+-- | Work around a bug in Typeable
127+-- (http://hackage.haskell.org/trac/ghc/ticket/5692)
128+compareWithoutFingerprint :: TypeRep -> TypeRep -> Bool
129+compareWithoutFingerprint (TypeRep _ con ts) (TypeRep _ con' ts')
130+ = con == con' && all (uncurry compareWithoutFingerprint) (zip ts ts')
131+
108132-- | Remove a 'Dynamic' constructor, provided that the recorded type matches the
109133-- type of the first static argument (the value of that argument is not used)
110134unDynamic :: Static a -> Process Dynamic -> Process a
111135unDynamic (Static label) pdyn = do
112136 Dynamic typ val <- pdyn
113− if typ == typeOfStaticLabel label
137+ if compareWithoutFingerprint typ (typeOfStaticLabel label) -- typ == typeOfStaticLabel label
114138 then return (unsafeCoerce# val)
115139 else fail $ "unDynamic: cannot match "
116140 ++ show typ
117141 ++ " against expected type "
118142 ++ show (typeOfStaticLabel label)
119143
120−-- | Dynamic bind
144+-- | Dynamic kleisli composition
121145--
122146-- The first argument stops remotable from trying to generate a SerializableDict
123147-- for (Process Dynamic, Process Dynamic)
124−bindDyn :: () -> (Process Dynamic, Process Dynamic) -> Process Dynamic
125−bindDyn () (px, pf) = do
126− x <- px
127− f <- pf
128− case dynBind tyConProcess bindProcess x f of
148+kleisliCompDyn :: () -> (Process Dynamic, Process Dynamic) -> Process Dynamic
149+kleisliCompDyn () (pf, pg) = do
150+ f <- pf -- a -> Process b
151+ g <- pg -- b -> Process c
152+ case dynKleisli tyConProcess kleisliProcess f g of
129153 Just dyn -> return dyn
130− Nothing -> fail $ "bindDyn: could not bind "
131− ++ show (dynTypeRep x)
132− ++ " to "
154+ Nothing -> fail $ "kleisliCompDyn: could not compose "
133155 ++ show (dynTypeRep f)
156+ ++ " with "
157+ ++ show (dynTypeRep g)
134158 where
135159 tyConProcess :: TyCon
136160 tyConProcess = typeRepTyCon (typeOf (undefined :: Process ()))
137161
162+cpFirstAux :: (a -> Process b) -> (a, c) -> Process (b, c)
163+cpFirstAux f (a, c) = f a >>= \b -> return (b, c)
164+
165+cpSecondAux :: (a -> Process b) -> (c, a) -> Process (c, b)
166+cpSecondAux f (c, a) = f a >>= \b -> return (c, b)
167+
138168---- Finally, the call to remotable --------------------------------------------
139169
140170remotable [ -- Monadic operations
141171 'returnProcess
142172 , 'bindProcess
143− , 'joinProcess
173+ , 'parJoinProcess
144174 -- CH primitives
145175 , 'link
146176 , 'unlink
@@ -149,13 +179,101 @@ remotable [ -- Monadic operations
149179 , 'expectDict
150180 , 'newChanDict
151181 -- Serialization dictionaries
152− , 'sdictBind
182+ , 'sdictComp
153183 -- Specialized processes
154184 , 'unClosure
155185 , 'unDynamic
156− , 'bindDyn
186+ , 'kleisliCompDyn
187+ , 'cpFirstAux
188+ , 'cpSecondAux
157189 ]
158190
191+--------------------------------------------------------------------------------
192+-- Some derived static functions --
193+--------------------------------------------------------------------------------
194+
195+staticUndynamic :: Typeable a => Static (Process Dynamic -> Process a)
196+staticUndynamic =
197+ $(mkStatic 'unDynamic) `staticApply` staticDuplicate (staticTypeOf (undefined :: a))
198+
199+staticKleisliCompDyn :: Static ((Process Dynamic, Process Dynamic) -> Process Dynamic)
200+staticKleisliCompDyn = $(mkStatic 'kleisliCompDyn) `staticApply` staticUnit
201+
202+--------------------------------------------------------------------------------
203+-- Definition of CP and the generalized arrow combinators --
204+--------------------------------------------------------------------------------
205+
206+-- | 'CP a b' represents the closure of a process parameterized by 'a' and
207+-- returning 'b'. 'CP a b' forms a (restricted) generalized arrow
208+-- (<http://www.cs.berkeley.edu/~megacz/garrows/>)
209+type CP a b = Closure (a -> Process b)
210+
211+-- | 'CP' introduction form
212+cpIntro :: forall a b. (Typeable a, Typeable b)
213+ => Closure (Process b) -> Closure (a -> Process b)
214+cpIntro (Closure static env) = Closure decoder env
215+ where
216+ decoder :: Static (ByteString -> a -> Process b)
217+ decoder = staticConst `staticCompose` static
218+
219+-- | 'CP' elimination form
220+cpElim :: forall a. Typeable a => CP () a -> Closure (Process a)
221+cpElim (Closure static env) = Closure decoder env
222+ where
223+ decoder :: Static (ByteString -> Process a)
224+ decoder = staticFlip static `staticApply` staticUnit
225+
226+-- | Identity ('Closure' version of 'return')
227+cpId :: Typeable a => CP a a
228+cpId = staticClosure $(mkStatic 'returnProcess)
229+
230+-- | Left-to-right composition ('Closure' version of '>=>')
231+cpComp :: forall a b c. (Typeable a, Typeable b, Typeable c)
232+ => CP a b -> CP b c -> CP a c
233+cpComp (Closure fstatic fenv) (Closure gstatic genv) =
234+ Closure decoder (encode (fenv, genv))
235+ where
236+ decoder :: Static (ByteString -> a -> Process c)
237+ decoder = $(mkStatic 'parJoinProcess)
238+ `staticCompose`
239+ staticUndynamic
240+ `staticCompose`
241+ staticKleisliCompDyn
242+ `staticCompose`
243+ (staticUnclosure fstatic `staticSplit` staticUnclosure gstatic)
244+ `staticCompose`
245+ staticDecode $(mkStatic 'sdictComp)
246+
247+-- | First
248+cpFirst :: forall a b c. (Typeable a, Typeable b, Typeable c)
249+ => CP a b -> CP (a, c) (b, c)
250+cpFirst (Closure static env) = Closure decoder env
251+ where
252+ decoder :: Static (ByteString -> (a, c) -> Process (b, c))
253+ decoder = $(mkStatic 'cpFirstAux) `staticCompose` static
254+
255+-- | Second
256+cpSecond :: forall a b c. (Typeable a, Typeable b, Typeable c)
257+ => CP a b -> CP (c, a) (c, b)
258+cpSecond (Closure static env) = Closure decoder env
259+ where
260+ decoder :: Static (ByteString -> (c, a) -> Process (c, b))
261+ decoder = $(mkStatic 'cpSecondAux) `staticCompose` static
262+
263+-- | Split (Like 'Control.Arrow.***')
264+cpSplit :: (Typeable a, Typeable b, Typeable c, Typeable d)
265+ => CP a c -> CP b d -> CP (a, b) (c, d)
266+cpSplit f g = cpFirst f `cpComp` cpSecond g
267+
268+-- | Left cancellation
269+cpCancelL :: Typeable a => CP ((), a) a
270+-- Closure (((), a) -> Process a)
271+cpCancelL = staticClosure ($(mkStatic 'returnProcess) `staticCompose` staticSnd)
272+
273+-- | Right cancellation
274+cpCancelR :: Typeable a => CP (a, ()) a
275+cpCancelR = staticClosure ($(mkStatic 'returnProcess) `staticCompose` staticFst)
276+
159277--------------------------------------------------------------------------------
160278-- Closure versions of CH primitives --
161279--------------------------------------------------------------------------------
@@ -208,37 +326,11 @@ staticUnclosure :: Typeable a
208326staticUnclosure s =
209327 $(mkStatic 'unClosure) `staticApply` staticDuplicate s
210328
211−staticUndynamic :: Typeable a => Static (Process Dynamic -> Process a)
212−staticUndynamic =
213− $(mkStatic 'unDynamic) `staticApply` staticDuplicate (staticTypeOf (undefined :: a))
214−
215−staticBindDyn :: Static ((Process Dynamic, Process Dynamic) -> Process Dynamic)
216−staticBindDyn = $(mkStatic 'bindDyn) `staticApply` staticUnit
217−
218329-- | Not-quite-monadic bind ('>>=')
219330cpBind :: forall a b. (Typeable a, Typeable b)
220331 => Closure (Process a) -> Closure (a -> Process b) -> Closure (Process b)
221−cpBind (Closure xstatic xenv) (Closure fstatic fenv) =
222− Closure decoder (encode (xenv, fenv))
223− where
224− decoder :: Static (ByteString -> Process b)
225− decoder = $(mkStatic 'joinProcess)
226− `staticCompose`
227− staticUndynamic
228− `staticCompose`
229− staticBindDyn
230− `staticCompose`
231− (staticUnclosure xstatic `staticSplit` staticUnclosure fstatic)
232− `staticCompose`
233− staticDecode $(mkStatic 'sdictBind)
332+cpBind x f = cpElim (cpIntro x `cpComp` f)
234333
235−cpIntro :: forall a b. (Typeable a, Typeable b)
236− => Closure (Process b) -> Closure (a -> Process b)
237−cpIntro (Closure static env) = Closure decoder env
238− where
239− decoder :: Static (ByteString -> a -> Process b)
240− decoder = staticConst `staticCompose` static
241−
242334-- | Monadic sequencing ('>>')
243335cpSeq :: Closure (Process ()) -> Closure (Process ()) -> Closure (Process ())
244336cpSeq p q = p `cpBind` cpIntro q
distributed-process/src/Control/Distributed/Process/Internal/Closure/Static.hs+32
@@ -3,6 +3,9 @@
33module Control.Distributed.Process.Internal.Closure.Static
44 ( -- * Static functionals
55 staticConst
6+ , staticFlip
7+ , staticFst
8+ , staticSnd
69 , staticCompose
710 , staticFirst
811 , staticSecond
@@ -16,6 +19,7 @@ module Control.Distributed.Process.Internal.Closure.Static
1619 -- * Serialization dictionaries (and their static versions)
1720 , sdictUnit
1821 , sdictProcessId
22+ , sdictSendPort
1923 -- * Runtime support
2024 , __remoteTable
2125 ) where
@@ -30,6 +34,7 @@ import Control.Distributed.Process.Internal.Types
3034 , Static
3135 , staticApply
3236 , ProcessId
37+ , SendPort
3338 )
3439import Control.Distributed.Process.Internal.Closure.TH (remotable, mkStatic)
3540import qualified Control.Arrow as Arrow (first, second, (***))
@@ -70,10 +75,16 @@ sdictUnit_ = SerializableDict
7075sdictProcessId_ :: SerializableDict ProcessId
7176sdictProcessId_ = SerializableDict
7277
78+sdictSendPort_ :: SerializableDict a -> SerializableDict (SendPort a)
79+sdictSendPort_ SerializableDict = SerializableDict
80+
7381---- Finally, the call to remotable --------------------------------------------
7482
7583remotable [ -- Functionals (predefined)
7684 'const
85+ , 'flip
86+ , 'fst
87+ , 'snd
7788 -- Functionals (defined above)
7889 , 'compose
7990 , 'first
@@ -86,6 +97,7 @@ remotable [ -- Functionals (predefined)
8697 -- Serialization dictionaries
8798 , 'sdictUnit_
8899 , 'sdictProcessId_
100+ , 'sdictSendPort_
89101 ]
90102
91103--------------------------------------------------------------------------------
@@ -97,6 +109,21 @@ remotable [ -- Functionals (predefined)
97109staticConst :: (Typeable a, Typeable b) => Static (a -> b -> a)
98110staticConst = $(mkStatic 'const)
99111
112+-- | Static version of 'flip'
113+staticFlip :: (Typeable a, Typeable b, Typeable c)
114+ => Static (a -> b -> c) -> Static (b -> a -> c)
115+staticFlip f = $(mkStatic 'flip) `staticApply` f
116+
117+-- | Static version of 'fst'
118+staticFst :: (Typeable a, Typeable b)
119+ => Static ((a, b) -> a)
120+staticFst = $(mkStatic 'fst)
121+
122+-- | Static version of 'snd'
123+staticSnd :: (Typeable a, Typeable b)
124+ => Static ((a, b) -> b)
125+staticSnd = $(mkStatic 'snd)
126+
100127-- | Static version of ('Prelude..')
101128staticCompose :: (Typeable a, Typeable b, Typeable c)
102129 => Static (b -> c) -> Static (a -> b) -> Static (a -> c)
@@ -137,6 +164,11 @@ sdictUnit = $(mkStatic 'sdictUnit_)
137164sdictProcessId :: Static (SerializableDict ProcessId)
138165sdictProcessId = $(mkStatic 'sdictProcessId_)
139166
167+-- | Serialization dictionary for 'SendPort'
168+sdictSendPort :: Typeable a
169+ => Static (SerializableDict a) -> Static (SerializableDict (SendPort a))
170+sdictSendPort = staticApply $(mkStatic 'sdictSendPort_)
171+
140172--------------------------------------------------------------------------------
141173-- Creating closures --
142174--------------------------------------------------------------------------------
distributed-process/src/Control/Distributed/Process/Internal/Dynamic.hs+31
@@ -15,6 +15,7 @@ module Control.Distributed.Process.Internal.Dynamic
1515 , dynApp
1616 , dynBind
1717 , dynBind'
18+ , dynKleisli
1819 , GHC.unsafeCoerce#
1920 ) where
2021
@@ -25,7 +26,9 @@ import Data.Typeable
2526 , funResultTy
2627 , TyCon
2728 , splitTyConApp
29+ , mkFunTy
2830 )
31+import Data.Typeable.Internal (funTc)
2932import qualified GHC.Prim as GHC (Any, unsafeCoerce#)
3033import Data.Maybe (fromMaybe)
3134
@@ -79,3 +82,31 @@ dynBind' :: TyCon -> (forall a b. m a -> (a -> m b) -> m b) -> Dynamic -> Dynami
7982dynBind' m bind x f = fromMaybe (error typeError) (dynBind m bind x f)
8083 where
8184 typeError = "Type error in dynamic bind.\nCan't bind " ++ show x ++ " to " ++ show f
85+
86+
87+
88+
89+-- | Dynamically typed Kleisli composition
90+dynKleisli :: TyCon
91+ -> (forall a b c. (a -> m b) -> (b -> m c) -> a -> m c)
92+ -> Dynamic
93+ -> Dynamic
94+ -> Maybe Dynamic
95+dynKleisli m comp (Dynamic tf f) (Dynamic tg g) =
96+ case splitTyConApp tf of
97+ (arr1, [a, mb]) | arr1 == funTc ->
98+ case splitTyConApp tg of
99+ (arr2, [b, mc]) | arr2 == funTc ->
100+ case splitTyConApp mb of
101+ (m', [b']) | m' == m && b' == b ->
102+ case splitTyConApp mc of
103+ (m'', [_c]) | m'' == m ->
104+ Just (Dynamic (mkFunTy a mc) (GHC.unsafeCoerce# comp f g))
105+ _ ->
106+ Nothing
107+ _ ->
108+ Nothing
109+ _ ->
110+ Nothing
111+ _ ->
112+ Nothing
distributed-process/tests/TestClosure.hs+29−1
@@ -2,7 +2,7 @@ module Main where
22
33import Data.ByteString.Lazy (empty)
44import Data.Typeable (Typeable, typeOf)
5−import Control.Monad (join, replicateM)
5+import Control.Monad (join, replicateM, forever)
66import Control.Exception (IOException, throw)
77import Control.Concurrent (forkIO, threadDelay)
88import Control.Concurrent.MVar (MVar, newEmptyMVar, readMVar, takeMVar, putMVar)
@@ -36,12 +36,22 @@ sendPid toPid = do
3636wait :: Int -> Process ()
3737wait = liftIO . threadDelay
3838
39+-- | First argument indicates empty closure environment
40+typedPingServer :: () -> ReceivePort (SendPort ()) -> Process ()
41+typedPingServer () rport = forever $ do
42+ us <- getSelfPid
43+ liftIO . putStrLn $ "Ping server " ++ show us ++ " waiting"
44+ sport <- receiveChan rport
45+ liftIO . putStrLn $ "Ping server " ++ show us ++ " replying on " ++ show (sendPortId sport)
46+ sendChan sport ()
47+
3948remotable [ 'factorial
4049 , 'addInt
4150 , 'putInt
4251 , 'sendPid
4352 , 'sdictInt
4453 , 'wait
54+ , 'typedPingServer
4555 ]
4656
4757factorialClosure :: Int -> Closure (Process Int)
@@ -276,6 +286,23 @@ testClosureExpect transport rtable = do
276286 (1234 :: Int) <- expect
277287 return ()
278288
289+testSpawnChannel :: Transport -> RemoteTable -> IO ()
290+testSpawnChannel transport rtable = do
291+ [node1, node2] <- replicateM 2 $ newLocalNode transport rtable
292+
293+ runProcess node1 $ do
294+ pingServer <- spawnChannel
295+ (sdictSendPort sdictUnit)
296+ (localNodeId node2)
297+ ($(mkClosure 'typedPingServer) ())
298+ (sendReply, receiveReply) <- newChan
299+ sendChan pingServer sendReply
300+ sendChan pingServer sendReply
301+ liftIO . putStrLn $ "client waiting for ping server to reply on " ++ show (sendPortId sendReply)
302+ () <- receiveChan receiveReply
303+ liftIO . putStrLn $ "client got reply"
304+ return ()
305+
279306main :: IO ()
280307main = do
281308 Right transport <- createTransport "127.0.0.1" "8080" defaultTCPParameters
@@ -293,4 +320,5 @@ main = do
293320 , ("SpawnSupervised", testSpawnSupervised transport rtable)
294321 , ("SpawnInvalid", testSpawnInvalid transport rtable)
295322 , ("ClosureExpect", testClosureExpect transport rtable)
323+ , ("SpawnChannel", testSpawnChannel transport rtable)
296324 ]