git.iko.soy/distributed-process
on master help sign in
Split Process into two modules

This now completely removes the mutual recursion in the module structure, and avoids some duplication of code.

commit83bdbe78de3906aa9c96554484b14b04e960eaad authorEdsko de Vries <edsko@edsko.net> dateFri 29 Jun 2012, 09:20 (14 years ago) parent07826d4
6 files changed+388−318
distributed-process/src/Control/Distributed/Process.hs+53−288 distributed-process/src/Control/Distributed/Process/Internal/Closure.hs+1−1 distributed-process/src/Control/Distributed/Process/Internal/Closure/BuiltIn.hs+2−1 distributed-process/src/Control/Distributed/Process/Internal/Closure/BuiltIn.hs-boot−15 distributed-process/src/Control/Distributed/Process/Internal/Closure/TH.hs+4−13 distributed-process/src/Control/Distributed/Process/Internal/Process/Primitives.hs+328
distributed-process/src/Control/Distributed/Process.hs+53−288
@@ -59,41 +59,22 @@ module Control.Distributed.Process
5959 , expectTimeout
6060 , spawnAsync
6161 , spawnSupervised
62+ , DidSpawn(..)
6263 ) where
6364
6465import Prelude hiding (catch)
65−import Data.Binary (decode)
6666import Data.Typeable (Typeable, typeOf)
67−import Control.Monad.Reader (ask)
68−import Control.Monad.IO.Class (MonadIO, liftIO)
6967import Control.Applicative ((<$>))
70−import Control.Exception (Exception, throw)
71−import qualified Control.Exception as Exception (catch)
72−import Control.Concurrent.MVar (modifyMVar)
73−import Control.Concurrent.Chan (writeChan)
74−import Control.Concurrent.STM
75− ( STM
76− , atomically
77− , orElse
78− , newTChan
79− , readTChan
80− , newTVar
81− , readTVar
82− , writeTVar
83− )
84−import Control.Distributed.Process.Internal.CQueue (dequeue, BlockSpec(..))
85−import Control.Distributed.Process.Serializable (Serializable, fingerprint)
86−import Data.Accessor ((^.), (^:), (^=))
68+import Control.Exception (throw)
69+import Control.Distributed.Process.Internal.MessageT (getLocalNode)
70+import Control.Distributed.Process.Internal.Dynamic (fromDyn, dynTypeRep)
8771import Control.Distributed.Process.Internal.Types
8872 ( RemoteTable
8973 , NodeId(..)
9074 , ProcessId(..)
91− , LocalNode(..)
92− , LocalProcess(..)
9375 , Process(..)
9476 , Closure(..)
9577 , Static(..)
96− , Message(..)
9778 , MonitorRef(..)
9879 , MonitorNotification(..)
9980 , LinkException(..)
@@ -102,34 +83,52 @@ import Control.Distributed.Process.Internal.Types
10283 , DiedReason(..)
10384 , SpawnRef(..)
10485 , DidSpawn(..)
105− , NCMsg(..)
106− , ProcessSignal(..)
107− , monitorCounter
108− , spawnCounter
10986 , Closure(..)
11087 , SendPort(..)
11188 , ReceivePort(..)
112− , channelCounter
113− , typedChannelWithId
114− , TypedChannel(..)
115− , ChannelId(..)
116− , Identifier(..)
117− , procMsg
11889 , SerializableDict(..)
90+ , procMsg
91+ , LocalNode(..)
11992 )
120−import Control.Distributed.Process.Internal.MessageT
121− ( sendMessage
122− , sendBinary
123− , getLocalNode
124− )
125−import Control.Distributed.Process.Internal.Dynamic (fromDyn, dynTypeRep)
126−import Control.Distributed.Process.Internal.Closure (resolveClosure)
127−import Control.Distributed.Process.Internal.Node (runLocalProcess)
128−import {-# SOURCE #-} Control.Distributed.Process.Internal.Closure.BuiltIn
93+import Control.Distributed.Process.Internal.Closure.BuiltIn
12994 ( linkClosure
13095 , sendClosure
13196 )
13297import Control.Distributed.Process.Internal.Closure.Combinators (cpSeq, cpBind)
98+import Control.Distributed.Process.Internal.Process.Primitives
99+ ( -- Basic messaging
100+ send
101+ , expect
102+ -- Channels
103+ , newChan
104+ , sendChan
105+ , receiveChan
106+ , mergePortsBiased
107+ , mergePortsRR
108+ -- Advanced messaging
109+ , Match
110+ , receiveWait
111+ , receiveTimeout
112+ , match
113+ , matchIf
114+ , matchUnknown
115+ -- Process management
116+ , spawn
117+ , terminate
118+ , ProcessTerminationException(..)
119+ , getSelfPid
120+ , getSelfNode
121+ -- Monitoring and linking
122+ , link
123+ , unlink
124+ , monitor
125+ , unmonitor
126+ -- Auxiliary API
127+ , catch
128+ , expectTimeout
129+ , spawnAsync
130+ )
131+import Control.Distributed.Process.Internal.Closure (resolveClosure)
133132
134133-- INTERNAL NOTES
135134--
@@ -191,134 +190,10 @@ import Control.Distributed.Process.Internal.Closure.Combinators (cpSeq, cpBind)
191190-- http://erlang.org/pipermail/erlang-questions/2012-February/064767.html
192191
193192--------------------------------------------------------------------------------
194−-- Basic messaging --
195−--------------------------------------------------------------------------------
196−
197−-- | Send a message
198−send :: Serializable a => ProcessId -> a -> Process ()
199−-- This requires a lookup on every send. If we want to avoid that we need to
200−-- modify serializable to allow for stateful (IO) deserialization
201−-- Warning: if we change how 'send' is implemented, might also want to change
202−-- the implementation of 'Internal.Closure.TH.generateSender'
203−send them msg = procMsg $ sendMessage (ProcessIdentifier them) msg
204−
205−-- | Wait for a message of a specific type
206−expect :: forall a. Serializable a => Process a
207−expect = receiveWait [match return]
208−
209−--------------------------------------------------------------------------------
210−-- Channels --
211−--------------------------------------------------------------------------------
212−
213−-- | Create a new typed channel
214−newChan :: Serializable a => Process (SendPort a, ReceivePort a)
215−newChan = do
216− proc <- ask
217− liftIO . modifyMVar (processState proc) $ \st -> do
218− chan <- liftIO . atomically $ newTChan
219− let lcid = st ^. channelCounter
220− cid = ChannelId { channelProcessId = processId proc
221− , channelLocalId = lcid
222− }
223− sport = SendPort cid
224− rport = ReceivePortSingle chan
225− tch = TypedChannel chan
226− return ( (channelCounter ^: (+ 1))
227− . (typedChannelWithId lcid ^= Just tch)
228− $ st
229− , (sport, rport)
230− )
231−
232−-- | Send a message on a typed channel
233−sendChan :: Serializable a => SendPort a -> a -> Process ()
234−sendChan (SendPort them) msg = procMsg $ sendBinary (ChannelIdentifier them) msg
235−
236−-- | Wait for a message on a typed channel
237−receiveChan :: Serializable a => ReceivePort a -> Process a
238−receiveChan = liftIO . atomically . receiveSTM
239− where
240− receiveSTM :: ReceivePort a -> STM a
241− receiveSTM (ReceivePortSingle c) =
242− readTChan c
243− receiveSTM (ReceivePortBiased ps) =
244− foldr1 orElse (map receiveSTM ps)
245− receiveSTM (ReceivePortRR psVar) = do
246− ps <- readTVar psVar
247− a <- foldr1 orElse (map receiveSTM ps)
248− writeTVar psVar (rotate ps)
249− return a
250−
251− rotate :: [a] -> [a]
252− rotate [] = []
253− rotate (x:xs) = xs ++ [x]
254−
255−-- | Merge a list of typed channels.
256−--
257−-- The result port is left-biased: if there are messages available on more
258−-- than one port, the first available message is returned.
259−mergePortsBiased :: Serializable a => [ReceivePort a] -> Process (ReceivePort a)
260−mergePortsBiased = return . ReceivePortBiased
261−
262−-- | Like 'mergePortsBiased', but with a round-robin scheduler (rather than
263−-- left-biased)
264−mergePortsRR :: Serializable a => [ReceivePort a] -> Process (ReceivePort a)
265−mergePortsRR ps = liftIO . atomically $ ReceivePortRR <$> newTVar ps
266−
267−--------------------------------------------------------------------------------
268−-- Advanced messaging --
193+-- Primitives are defined in a separate module; here we only define derived --
194+-- constructs --
269195--------------------------------------------------------------------------------
270196
271−-- | Opaque type used in 'receiveWait' and 'receiveTimeout'
272−newtype Match b = Match { unMatch :: Message -> Maybe (Process b) }
273−
274−-- | Test the matches in order against each message in the queue
275−receiveWait :: [Match b] -> Process b
276−receiveWait ms = do
277− queue <- processQueue <$> ask
278− Just proc <- liftIO $ dequeue queue Blocking (map unMatch ms)
279− proc
280−
281−-- | Like 'receiveWait' but with a timeout.
282−--
283−-- If the timeout is zero do a non-blocking check for matching messages.
284−receiveTimeout :: Int -> [Match b] -> Process (Maybe b)
285−receiveTimeout t ms = do
286− queue <- processQueue <$> ask
287− let blockSpec = if t == 0 then NonBlocking else Timeout t
288− mProc <- liftIO $ dequeue queue blockSpec (map unMatch ms)
289− case mProc of
290− Nothing -> return Nothing
291− Just proc -> Just <$> proc
292−
293−-- | Match against any message of the right type
294−match :: forall a b. Serializable a => (a -> Process b) -> Match b
295−match = matchIf (const True)
296−
297−-- | Match against any message of the right type that satisfies a predicate
298−matchIf :: forall a b. Serializable a => (a -> Bool) -> (a -> Process b) -> Match b
299−matchIf c p = Match $ \msg ->
300− let decoded :: a
301− decoded = decode . messageEncoding $ msg in
302− if messageFingerprint msg == fingerprint (undefined :: a) && c decoded
303− then Just $ p decoded
304− else Nothing
305−
306−-- | Remove any message from the queue
307−matchUnknown :: Process b -> Match b
308−matchUnknown = Match . const . Just
309−
310−--------------------------------------------------------------------------------
311−-- Process management --
312−--------------------------------------------------------------------------------
313−
314−-- | Spawn a process
315−spawn :: NodeId -> Closure (Process ()) -> Process ProcessId
316−spawn nid proc = do
317− ref <- spawnAsync nid proc
318− receiveWait [ matchIf (\(DidSpawn ref' _) -> ref == ref')
319− (\(DidSpawn _ pid) -> return pid)
320− ]
321−
322197-- | Run a process remotely and wait for it to reply
323198call :: SerializableDict a -> NodeId -> Closure (Process a) -> Process a
324199call sdict@SerializableDict nid proc = do
@@ -326,90 +201,6 @@ call sdict@SerializableDict nid proc = do
326201 spawn nid (proc `cpBind` sendClosure sdict us)
327202 expect
328203
329−-- | Thrown by 'terminate'
330−data ProcessTerminationException = ProcessTerminationException
331− deriving (Show, Typeable)
332−
333−instance Exception ProcessTerminationException
334−
335−-- | Terminate (throws a ProcessTerminationException)
336−terminate :: Process a
337−terminate = liftIO $ throw ProcessTerminationException
338−
339−-- | Our own process ID
340−getSelfPid :: Process ProcessId
341−getSelfPid = processId <$> ask
342−
343−-- | Get the node ID of our local node
344−getSelfNode :: Process NodeId
345−getSelfNode = localNodeId <$> procMsg getLocalNode
346−
347−--------------------------------------------------------------------------------
348−-- Monitoring and linking --
349−--------------------------------------------------------------------------------
350−
351−-- | Link to a remote process (asynchronous)
352−--
353−-- Note that 'link' provides unidirectional linking (see 'spawnSupervised').
354−link :: ProcessId -> Process ()
355−link = sendCtrlMsg Nothing . Link
356−
357−-- | Remove a link (asynchronous)
358−unlink :: ProcessId -> Process ()
359−unlink = sendCtrlMsg Nothing . Unlink
360−
361−-- | Monitor another process (asynchronous)
362−monitor :: ProcessId -> Process MonitorRef
363−monitor them = do
364− monitorRef <- getMonitorRefFor them
365− sendCtrlMsg Nothing $ Monitor them monitorRef
366− return monitorRef
367−
368−-- | Remove a monitor (asynchronous)
369−unmonitor :: MonitorRef -> Process ()
370−unmonitor = sendCtrlMsg Nothing . Unmonitor
371−
372−--------------------------------------------------------------------------------
373−-- Closures --
374−--------------------------------------------------------------------------------
375−
376−-- | Deserialize a closure
377−unClosure :: forall a. Typeable a => Closure a -> Process a
378−unClosure (Closure (Static label) env) = do
379− rtable <- remoteTable <$> procMsg getLocalNode
380− let Just dyn = resolveClosure rtable label env
381− return $ fromDyn dyn (throw (typeError dyn))
382− where
383− typeError dyn = userError $ "lookupStatic type error: "
384− ++ "cannot match " ++ show (dynTypeRep dyn)
385− ++ " against " ++ show (typeOf (undefined :: a))
386−
387−--------------------------------------------------------------------------------
388−-- Auxiliary API --
389−--------------------------------------------------------------------------------
390−
391−-- | Catch exceptions within a process
392−catch :: Exception e => Process a -> (e -> Process a) -> Process a
393−catch p h = do
394− node <- procMsg getLocalNode
395− lproc <- ask
396− let run :: Process a -> IO a
397− run proc = runLocalProcess node proc lproc
398− liftIO $ Exception.catch (run p) (run . h)
399−
400−-- | Like 'expect' but with a timeout
401−expectTimeout :: forall a. Serializable a => Int -> Process (Maybe a)
402−expectTimeout timeout = receiveTimeout timeout [match return]
403−
404−-- | Asynchronous version of 'spawn'
405−--
406−-- ('spawn' is defined in terms of 'spawnAsync' and 'expect')
407−spawnAsync :: NodeId -> Closure (Process ()) -> Process SpawnRef
408−spawnAsync nid proc = do
409− spawnRef <- getSpawnRef
410− sendCtrlMsg (Just nid) $ Spawn proc spawnRef
411− return spawnRef
412−
413204-- | Spawn a child process, have the child link to the parent and the parent
414205-- monitor the child
415206spawnSupervised :: NodeId
@@ -421,40 +212,14 @@ spawnSupervised nid proc = do
421212 ref <- monitor them
422213 return (them, ref)
423214
424−--------------------------------------------------------------------------------
425−-- Auxiliary functions --
426−--------------------------------------------------------------------------------
427−
428−getMonitorRefFor :: ProcessId -> Process MonitorRef
429−getMonitorRefFor pid = do
430− proc <- ask
431− liftIO $ modifyMVar (processState proc) $ \st -> do
432− let counter = st ^. monitorCounter
433− return ( monitorCounter ^: (+ 1) $ st
434− , MonitorRef pid counter
435− )
436−
437−getSpawnRef :: Process SpawnRef
438−getSpawnRef = do
439− proc <- ask
440− liftIO $ modifyMVar (processState proc) $ \st -> do
441− let counter = st ^. spawnCounter
442− return ( spawnCounter ^: (+ 1) $ st
443− , SpawnRef counter
444− )
215+-- | Deserialize a closure
216+unClosure :: forall a. Typeable a => Closure a -> Process a
217+unClosure (Closure (Static label) env) = do
218+ rtable <- remoteTable <$> procMsg getLocalNode
219+ let Just dyn = resolveClosure rtable label env
220+ return $ fromDyn dyn (throw (typeError dyn))
221+ where
222+ typeError dyn = userError $ "lookupStatic type error: "
223+ ++ "cannot match " ++ show (dynTypeRep dyn)
224+ ++ " against " ++ show (typeOf (undefined :: a))
445225
446−-- Send a control message
447−sendCtrlMsg :: Maybe NodeId -- ^ Nothing for the local node
448− -> ProcessSignal -- ^ Message to send
449− -> Process ()
450−sendCtrlMsg mNid signal = do
451− us <- getSelfPid
452− let msg = NCMsg { ctrlMsgSender = ProcessIdentifier us
453− , ctrlMsgSignal = signal
454− }
455− case mNid of
456− Nothing -> do
457− ctrlChan <- localCtrlChan <$> procMsg getLocalNode
458− liftIO $ writeChan ctrlChan msg
459− Just nid ->
460− procMsg $ sendBinary (NodeIdentifier nid) msg
distributed-process/src/Control/Distributed/Process/Internal/Closure.hs+1−1
@@ -29,7 +29,7 @@ import Control.Distributed.Process.Internal.Dynamic
2929 , unsafeCoerce#
3030 )
3131import Control.Distributed.Process.Internal.TypeRep () -- Binary instances
32−import {-# SOURCE #-} qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn
32+import qualified Control.Distributed.Process.Internal.Closure.BuiltIn as BuiltIn
3333 (remoteTable)
3434
3535--------------------------------------------------------------------------------
distributed-process/src/Control/Distributed/Process/Internal/Closure/BuiltIn.hs+2−1
@@ -8,7 +8,7 @@ module Control.Distributed.Process.Internal.Closure.BuiltIn
88
99import Data.Binary (encode)
1010import Data.Typeable (typeOf)
11−import Control.Distributed.Process (link)
11+import Control.Distributed.Process.Internal.Process.Primitives (link)
1212import Control.Distributed.Process.Internal.Types
1313 ( ProcessId
1414 , Closure
@@ -20,6 +20,7 @@ import Control.Distributed.Process.Internal.Types
2020 , StaticLabel(..)
2121 )
2222import Control.Distributed.Process.Internal.Closure.TH (remotable, mkClosure)
23+import Control.Distributed.Process.Internal.TypeRep () -- Binary instances
2324
2425remotable ['link]
2526
distributed-process/src/Control/Distributed/Process/Internal/Closure/BuiltIn.hs-bootdeleted−15
@@ -1,15 +0,0 @@
1−module Control.Distributed.Process.Internal.Closure.BuiltIn where
2−
3−import Control.Distributed.Process.Internal.Types
4− ( ProcessId
5− , Closure
6− , Process
7− , RemoteTable
8− , SerializableDict
9− )
10−
11−remoteTable :: RemoteTable -> RemoteTable
12−
13−linkClosure :: ProcessId -> Closure (Process ())
14−sendClosure :: SerializableDict a -> ProcessId -> Closure (a -> Process ())
15−returnClosure :: SerializableDict a -> a -> Closure (Process a)
distributed-process/src/Control/Distributed/Process/Internal/Closure/TH.hs+4−13
@@ -46,15 +46,13 @@ import Control.Distributed.Process.Internal.Types
4646 , StaticLabel(..)
4747 , Process
4848 , ProcessId
49− , procMsg
50− , Identifier(ProcessIdentifier)
5149 , remoteTableLabel
5250 , remoteTableDict
5351 , SerializableDict(..)
5452 , RuntimeSerializableSupport(..)
5553 )
5654import Control.Distributed.Process.Internal.Dynamic (Dynamic, toDyn)
57−import Control.Distributed.Process.Internal.MessageT (sendMessage)
55+import Control.Distributed.Process.Internal.Process.Primitives (send)
5856
5957--------------------------------------------------------------------------------
6058-- User-level API --
@@ -134,16 +132,9 @@ registerLabel label dyn = remoteTableLabel label ^= Just dyn
134132
135133registerSerializableDict :: forall a. SerializableDict a -> RemoteTable -> RemoteTable
136134registerSerializableDict SerializableDict =
137− -- TODO: Code duplication with Process
138− let send :: ProcessId -> a -> Process ()
139− send pid a = procMsg $ sendMessage (ProcessIdentifier pid) a
140−
141− ret :: ByteString -> Process a
142− ret = return . decode
143−
144− rss = RuntimeSerializableSupport {
145− rssSend = toDyn send
146− , rssReturn = toDyn ret
135+ let rss = RuntimeSerializableSupport {
136+ rssSend = toDyn (send :: ProcessId -> a -> Process ())
137+ , rssReturn = toDyn (return . decode :: ByteString -> Process a)
147138 }
148139 in remoteTableDict (typeOf (undefined :: a)) ^= Just rss
149140
distributed-process/src/Control/Distributed/Process/Internal/Process/Primitives.hsnew file+328
@@ -0,0 +1,328 @@
1+-- | Cloud Haskell primitives
2+--
3+-- We define these in a separate module so that we don't have to rely on
4+-- the closure combinators
5+module Control.Distributed.Process.Internal.Process.Primitives
6+ ( -- * Basic messaging
7+ send
8+ , expect
9+ -- * Channels
10+ , newChan
11+ , sendChan
12+ , receiveChan
13+ , mergePortsBiased
14+ , mergePortsRR
15+ -- * Advanced messaging
16+ , Match
17+ , receiveWait
18+ , receiveTimeout
19+ , match
20+ , matchIf
21+ , matchUnknown
22+ -- * Process management
23+ , spawn
24+ , terminate
25+ , ProcessTerminationException(..)
26+ , getSelfPid
27+ , getSelfNode
28+ -- * Monitoring and linking
29+ , link
30+ , unlink
31+ , monitor
32+ , unmonitor
33+ -- * Auxiliary API
34+ , catch
35+ , expectTimeout
36+ , spawnAsync
37+ ) where
38+
39+import Prelude hiding (catch)
40+import Data.Binary (decode)
41+import Data.Typeable (Typeable,)
42+import Control.Monad.Reader (ask)
43+import Control.Monad.IO.Class (MonadIO, liftIO)
44+import Control.Applicative ((<$>))
45+import Control.Exception (Exception, throw)
46+import qualified Control.Exception as Exception (catch)
47+import Control.Concurrent.MVar (modifyMVar)
48+import Control.Concurrent.Chan (writeChan)
49+import Control.Concurrent.STM
50+ ( STM
51+ , atomically
52+ , orElse
53+ , newTChan
54+ , readTChan
55+ , newTVar
56+ , readTVar
57+ , writeTVar
58+ )
59+import Control.Distributed.Process.Internal.CQueue (dequeue, BlockSpec(..))
60+import Control.Distributed.Process.Serializable (Serializable, fingerprint)
61+import Data.Accessor ((^.), (^:), (^=))
62+import Control.Distributed.Process.Internal.Types
63+ ( NodeId(..)
64+ , ProcessId(..)
65+ , LocalNode(..)
66+ , LocalProcess(..)
67+ , Process(..)
68+ , Closure(..)
69+ , Message(..)
70+ , MonitorRef(..)
71+ , SpawnRef(..)
72+ , DidSpawn(..)
73+ , NCMsg(..)
74+ , ProcessSignal(..)
75+ , monitorCounter
76+ , spawnCounter
77+ , Closure(..)
78+ , SendPort(..)
79+ , ReceivePort(..)
80+ , channelCounter
81+ , typedChannelWithId
82+ , TypedChannel(..)
83+ , ChannelId(..)
84+ , Identifier(..)
85+ , procMsg
86+ )
87+import Control.Distributed.Process.Internal.MessageT
88+ ( sendMessage
89+ , sendBinary
90+ , getLocalNode
91+ )
92+import Control.Distributed.Process.Internal.Node (runLocalProcess)
93+
94+--------------------------------------------------------------------------------
95+-- Basic messaging --
96+--------------------------------------------------------------------------------
97+
98+-- | Send a message
99+send :: Serializable a => ProcessId -> a -> Process ()
100+-- This requires a lookup on every send. If we want to avoid that we need to
101+-- modify serializable to allow for stateful (IO) deserialization
102+-- Warning: if we change how 'send' is implemented, might also want to change
103+-- the implementation of 'Internal.Closure.TH.generateSender'
104+send them msg = procMsg $ sendMessage (ProcessIdentifier them) msg
105+
106+-- | Wait for a message of a specific type
107+expect :: forall a. Serializable a => Process a
108+expect = receiveWait [match return]
109+
110+--------------------------------------------------------------------------------
111+-- Channels --
112+--------------------------------------------------------------------------------
113+
114+-- | Create a new typed channel
115+newChan :: Serializable a => Process (SendPort a, ReceivePort a)
116+newChan = do
117+ proc <- ask
118+ liftIO . modifyMVar (processState proc) $ \st -> do
119+ chan <- liftIO . atomically $ newTChan
120+ let lcid = st ^. channelCounter
121+ cid = ChannelId { channelProcessId = processId proc
122+ , channelLocalId = lcid
123+ }
124+ sport = SendPort cid
125+ rport = ReceivePortSingle chan
126+ tch = TypedChannel chan
127+ return ( (channelCounter ^: (+ 1))
128+ . (typedChannelWithId lcid ^= Just tch)
129+ $ st
130+ , (sport, rport)
131+ )
132+
133+-- | Send a message on a typed channel
134+sendChan :: Serializable a => SendPort a -> a -> Process ()
135+sendChan (SendPort them) msg = procMsg $ sendBinary (ChannelIdentifier them) msg
136+
137+-- | Wait for a message on a typed channel
138+receiveChan :: Serializable a => ReceivePort a -> Process a
139+receiveChan = liftIO . atomically . receiveSTM
140+ where
141+ receiveSTM :: ReceivePort a -> STM a
142+ receiveSTM (ReceivePortSingle c) =
143+ readTChan c
144+ receiveSTM (ReceivePortBiased ps) =
145+ foldr1 orElse (map receiveSTM ps)
146+ receiveSTM (ReceivePortRR psVar) = do
147+ ps <- readTVar psVar
148+ a <- foldr1 orElse (map receiveSTM ps)
149+ writeTVar psVar (rotate ps)
150+ return a
151+
152+ rotate :: [a] -> [a]
153+ rotate [] = []
154+ rotate (x:xs) = xs ++ [x]
155+
156+-- | Merge a list of typed channels.
157+--
158+-- The result port is left-biased: if there are messages available on more
159+-- than one port, the first available message is returned.
160+mergePortsBiased :: Serializable a => [ReceivePort a] -> Process (ReceivePort a)
161+mergePortsBiased = return . ReceivePortBiased
162+
163+-- | Like 'mergePortsBiased', but with a round-robin scheduler (rather than
164+-- left-biased)
165+mergePortsRR :: Serializable a => [ReceivePort a] -> Process (ReceivePort a)
166+mergePortsRR ps = liftIO . atomically $ ReceivePortRR <$> newTVar ps
167+
168+--------------------------------------------------------------------------------
169+-- Advanced messaging --
170+--------------------------------------------------------------------------------
171+
172+-- | Opaque type used in 'receiveWait' and 'receiveTimeout'
173+newtype Match b = Match { unMatch :: Message -> Maybe (Process b) }
174+
175+-- | Test the matches in order against each message in the queue
176+receiveWait :: [Match b] -> Process b
177+receiveWait ms = do
178+ queue <- processQueue <$> ask
179+ Just proc <- liftIO $ dequeue queue Blocking (map unMatch ms)
180+ proc
181+
182+-- | Like 'receiveWait' but with a timeout.
183+--
184+-- If the timeout is zero do a non-blocking check for matching messages.
185+receiveTimeout :: Int -> [Match b] -> Process (Maybe b)
186+receiveTimeout t ms = do
187+ queue <- processQueue <$> ask
188+ let blockSpec = if t == 0 then NonBlocking else Timeout t
189+ mProc <- liftIO $ dequeue queue blockSpec (map unMatch ms)
190+ case mProc of
191+ Nothing -> return Nothing
192+ Just proc -> Just <$> proc
193+
194+-- | Match against any message of the right type
195+match :: forall a b. Serializable a => (a -> Process b) -> Match b
196+match = matchIf (const True)
197+
198+-- | Match against any message of the right type that satisfies a predicate
199+matchIf :: forall a b. Serializable a => (a -> Bool) -> (a -> Process b) -> Match b
200+matchIf c p = Match $ \msg ->
201+ let decoded :: a
202+ decoded = decode . messageEncoding $ msg in
203+ if messageFingerprint msg == fingerprint (undefined :: a) && c decoded
204+ then Just $ p decoded
205+ else Nothing
206+
207+-- | Remove any message from the queue
208+matchUnknown :: Process b -> Match b
209+matchUnknown = Match . const . Just
210+
211+--------------------------------------------------------------------------------
212+-- Process management --
213+--------------------------------------------------------------------------------
214+
215+-- | Spawn a process
216+spawn :: NodeId -> Closure (Process ()) -> Process ProcessId
217+spawn nid proc = do
218+ ref <- spawnAsync nid proc
219+ receiveWait [ matchIf (\(DidSpawn ref' _) -> ref == ref')
220+ (\(DidSpawn _ pid) -> return pid)
221+ ]
222+
223+-- | Thrown by 'terminate'
224+data ProcessTerminationException = ProcessTerminationException
225+ deriving (Show, Typeable)
226+
227+instance Exception ProcessTerminationException
228+
229+-- | Terminate (throws a ProcessTerminationException)
230+terminate :: Process a
231+terminate = liftIO $ throw ProcessTerminationException
232+
233+-- | Our own process ID
234+getSelfPid :: Process ProcessId
235+getSelfPid = processId <$> ask
236+
237+-- | Get the node ID of our local node
238+getSelfNode :: Process NodeId
239+getSelfNode = localNodeId <$> procMsg getLocalNode
240+
241+--------------------------------------------------------------------------------
242+-- Monitoring and linking --
243+--------------------------------------------------------------------------------
244+
245+-- | Link to a remote process (asynchronous)
246+--
247+-- Note that 'link' provides unidirectional linking (see 'spawnSupervised').
248+link :: ProcessId -> Process ()
249+link = sendCtrlMsg Nothing . Link
250+
251+-- | Remove a link (asynchronous)
252+unlink :: ProcessId -> Process ()
253+unlink = sendCtrlMsg Nothing . Unlink
254+
255+-- | Monitor another process (asynchronous)
256+monitor :: ProcessId -> Process MonitorRef
257+monitor them = do
258+ monitorRef <- getMonitorRefFor them
259+ sendCtrlMsg Nothing $ Monitor them monitorRef
260+ return monitorRef
261+
262+-- | Remove a monitor (asynchronous)
263+unmonitor :: MonitorRef -> Process ()
264+unmonitor = sendCtrlMsg Nothing . Unmonitor
265+
266+--------------------------------------------------------------------------------
267+-- Auxiliary API --
268+--------------------------------------------------------------------------------
269+
270+-- | Catch exceptions within a process
271+catch :: Exception e => Process a -> (e -> Process a) -> Process a
272+catch p h = do
273+ node <- procMsg getLocalNode
274+ lproc <- ask
275+ let run :: Process a -> IO a
276+ run proc = runLocalProcess node proc lproc
277+ liftIO $ Exception.catch (run p) (run . h)
278+
279+-- | Like 'expect' but with a timeout
280+expectTimeout :: forall a. Serializable a => Int -> Process (Maybe a)
281+expectTimeout timeout = receiveTimeout timeout [match return]
282+
283+-- | Asynchronous version of 'spawn'
284+--
285+-- ('spawn' is defined in terms of 'spawnAsync' and 'expect')
286+spawnAsync :: NodeId -> Closure (Process ()) -> Process SpawnRef
287+spawnAsync nid proc = do
288+ spawnRef <- getSpawnRef
289+ sendCtrlMsg (Just nid) $ Spawn proc spawnRef
290+ return spawnRef
291+
292+--------------------------------------------------------------------------------
293+-- Auxiliary functions --
294+--------------------------------------------------------------------------------
295+
296+getMonitorRefFor :: ProcessId -> Process MonitorRef
297+getMonitorRefFor pid = do
298+ proc <- ask
299+ liftIO $ modifyMVar (processState proc) $ \st -> do
300+ let counter = st ^. monitorCounter
301+ return ( monitorCounter ^: (+ 1) $ st
302+ , MonitorRef pid counter
303+ )
304+
305+getSpawnRef :: Process SpawnRef
306+getSpawnRef = do
307+ proc <- ask
308+ liftIO $ modifyMVar (processState proc) $ \st -> do
309+ let counter = st ^. spawnCounter
310+ return ( spawnCounter ^: (+ 1) $ st
311+ , SpawnRef counter
312+ )
313+
314+-- Send a control message
315+sendCtrlMsg :: Maybe NodeId -- ^ Nothing for the local node
316+ -> ProcessSignal -- ^ Message to send
317+ -> Process ()
318+sendCtrlMsg mNid signal = do
319+ us <- getSelfPid
320+ let msg = NCMsg { ctrlMsgSender = ProcessIdentifier us
321+ , ctrlMsgSignal = signal
322+ }
323+ case mNid of
324+ Nothing -> do
325+ ctrlChan <- localCtrlChan <$> procMsg getLocalNode
326+ liftIO $ writeChan ctrlChan msg
327+ Just nid ->
328+ procMsg $ sendBinary (NodeIdentifier nid) msg