@@ -8,34 +8,38 @@ title: Getting to know Processes
88### The Thing About Nodes
99
1010Before we can really get to know _ processes_ , we need to consider the role of
11- the _ Node Controller_ in Cloud Haskell. As per the [ _ semantics_ ] [ 4 ] , Cloud
12- Haskell makes the role of _ Node Controller_ (occasionally referred to by the original
13- "Unified Semantics for Future Erlang" paper on which our semantics are modelled
14- as the "ether") explicit.
11+ the _ Node Controller_ in Cloud Haskell. In our formal [ _ semantics_ ] [ 4 ] , Cloud
12+ Haskell hides the role of _ Node Controller_ (explicitly defined in the original
13+ "Unified Semantics for Future Erlang" paper on which our semantics are modelled).
14+ Nonetheless, each Cloud Haskell _ node_ is serviced and managed by a
15+ conceptual _ Node Controller_ .
1516
1617Architecturally, Cloud Haskell's _ Node Controller_ consists of a pair of message
1718buss processes, one of which listens for network-transport level events whilst the
1819other is busy processing _ signal events_ (most of which pertain to either message
1920delivery or process lifecycle notification). Both these _ event loops_ runs sequentially
20- in the system at all times.
21+ in the system at all times. Messages are delivered via the _ Node Controller's_
22+ _ event loops_ , which broadly correspond to the _ system queue (or "ether")_ mentioned
23+ in the [ _ semantics_ ] [ 4 ] . The _ system queue_ delivers messages to individual process
24+ mailboxes in a completely transparent fashion, leaving us with the illusion that
25+ processes exist in a unidimensional space.
2126
22- With this in mind, let's consider Cloud Haskell's lightweight processes in a bit
27+ With all this in mind, let's consider Cloud Haskell's lightweight processes in a bit
2328more detail...
2429
2530### Message Ordering
2631
27- We have already met the ` send ` primitive, which is used to deliver
28- a message to another process. Here's a review of what we've learned
29- about ` send ` thus far:
32+ We have already met the ` send ` primitive, used to deliver messages from one
33+ process to another. Here's a review of what we've learned about ` send ` thus far:
3034
31351 . sending is asynchronous (i.e., it does not block the caller)
32362 . sending _ never_ fails, regardless of the state of the recipient process
33373 . even if a message is received, there is ** no** guarantee * when* it will arrive
34384 . there are ** no** guarantees that the message will be received at all
3539
3640Asynchronous sending buys us several benefits. Improved concurrency is
37- possible, because processes do not need to block and wait for acknowledgements
38- and error handling need not be implemented each time a message is sent.
41+ possible, because processes need not block or wait for acknowledgements,
42+ nor does error handling need to be implemented each time a message is sent.
3943Consider a stream of messages sent from one process to another. If the
4044stream consists of messages ` a, b, c ` and we have seen ` c ` , then we know for
4145certain that we will have already seen ` a, b ` (in that order), so long as the
@@ -58,15 +62,14 @@ their mailbox.
5862
5963Processes dequeue messages (from their mailbox) using the [ ` expect ` ] [ 1 ]
6064and [ ` recieve ` ] [ 2 ] family of primitives. Both take an optional timeout,
61- which leads to the expression evaluating to ` Nothing ` if no matching input
65+ allowing the expression to evaluate to ` Nothing ` if no matching input
6266is found.
6367
6468The [ ` expect ` ] [ 1 ] primitive blocks until a message matching the expected type
65- (of the expression) is found in the process' mailbox. If such a message can be
66- found by scanning the mailbox, it is dequeued and given to the caller. If no
67- message (matching the expected type) can be found, the caller (i.e., the
68- calling thread) is blocked until a matching message is delivered to the mailbox.
69- Let's take a look at this in action:
69+ (of the expression) is found in the process' mailbox. If a match is found by
70+ scanning the mailbox, it is dequeued and given to the caller, otherwise the
71+ caller (i.e., the calling thread) is blocked until a message of the expected
72+ type is delivered to the mailbox. Let's take a look at this in action:
7073
7174{% highlight haskell %}
7275demo :: Process ()
@@ -79,23 +82,23 @@ demo = do
7982 listen = do
8083 third <- expect :: Process ProcessId
8184 first <- expect :: Process String
82- Nothing <- expectTimeout 100000 :: Process String
83- say first
85+ second <- expectTimeout 100000 :: Process String
86+ mapM _ ( say . show) [ first, second, third ]
8487 send third ()
8588{% endhighlight %}
8689
8790This program will print ` "hello" ` , then ` Nothing ` and finally ` pid://... ` .
88- The first ` expect ` - labelled "third" because of the order in which it is
89- due to be received - ** will** succeed, since the parent process sends its
90- ` ProcessId ` after the string "hello", yet the listener blocks until it can dequeue
91- the ` ProcessId ` before "expecting" a string. The second ` expect ` (labelled "first")
92- also succeeds, demonstrating that the listener has selectively removed messages
93- from its mailbox based on their type rather than the order in which they arrived.
94- The third ` expect ` will timeout and evaluate to ` Nothing ` , because only one string
95- is ever sent to the listener and that has already been removed from the mailbox.
96- The removal of messages from the process' mailbox based on type is what makes this
97- program viable - without this "selective receiving", the program would block and
98- never complete.
91+ The first ` expect ` - labelled "third" because of the order in which we
92+ know it will arrive in our mailbox - ** will** succeed, since the parent process
93+ sends its ` ProcessId ` after the string "hello", yet the listener blocks until it
94+ can dequeue the ` ProcessId ` before "expecting" a string. The second ` expect `
95+ (labelled "first") also succeeds, demonstrating that the listener has selectively
96+ removed messages from its mailbox based on their type rather than the order in
97+ which they arrived. The third ` expect ` will timeout and evaluate to ` Nothing ` ,
98+ because only one string is ever sent to the listener and that has already been
99+ removed from the mailbox. The removal of messages from the process' mailbox based
100+ on type is what makes this program viable - without this "selective receiving",
101+ the program would block and never complete.
99102
100103By contrast, the [ ` recieve ` ] [ 2 ] family of primitives take a list of ` Match `
101104objects, each derived from evaluating a [ ` match ` ] [ 3 ] style primitive. This
@@ -292,7 +295,47 @@ some `DiedReason` other than `DiedNormal`).
292295Monitors on the other hand, do not cause the * listening* process to exit at all, instead
293296putting a ` ProcessMonitorNotification ` into the process' mailbox. This signal and its
294297constituent fields can be introspected in order to decide what action (if any) the receiver
295- can/should take in response to the monitored processes death.
298+ can/should take in response to the monitored processes death. Let's take a look at how
299+ monitors can be used to determine both when and _ how_ a process has terminated. Tucked
300+ away in distributed-process-platform, the ` linkOnFailure ` primitive works just like our
301+ built-in ` link ` except that it only terminates the process which evaluated it (the
302+ _ linker_ ), if the process it is linking with (the _ linkee_ ) terminates abnormally.
303+ Let's take a look...
304+
305+ {% highlight haskell %}
306+ linkOnFailure them = do
307+ us <- getSelfPid
308+ tid <- liftIO $ myThreadId
309+ void $ spawnLocal $ do
310+ callerRef <- P.monitor us
311+ calleeRef <- P.monitor them
312+ reason <- receiveWait [
313+ matchIf (\( ProcessMonitorNotification mRef _ _ ) ->
314+ mRef == callerRef) -- nothing left to do
315+ (\_ -> return DiedNormal)
316+ , matchIf (\( ProcessMonitorNotification mRef' _ _ ) ->
317+ mRef' == calleeRef)
318+ (\( ProcessMonitorNotification _ _ r') -> return r')
319+ ]
320+ case reason of
321+ DiedNormal -> return ()
322+ _ -> liftIO $ throwTo tid (ProcessLinkException us reason)
323+ {% endhighlight %}
324+
325+ As we can see, this code makes use of monitors to track both processes involved in the
326+ link. In order to track _ both_ processes and react to changes in their status, it is
327+ necessary to spawn a third process which will do the monitoring. This doesn't happen
328+ with the built-in link primitive, but is necessary in this case since the link handling
329+ code resides outside the _ Node Controller_ .
330+
331+ The two matches passed to ` receiveWait ` both handle a ` ProcessMonitorNotification ` , and
332+ the predicate passed to ` matchIf ` is used to determine whether the notification we're
333+ receiving is for the _ linker_ or the _ linkee_ . If the _ linker_ dies, we've nothing more
334+ to do, since links are unidirectional. If the _ linkee_ dies however, we must examine
335+ the ` DiedReason ` the ` ProcessMonitorNotification ` provides us with, to determine whether
336+ the _ linkee_ exited normally (i.e., with ` DiedNormal ` ) or otherwise. In the latter case,
337+ we throw a ` ProcessLinkException ` to the _ linker_ , which is exactly how an ordinary link
338+ would behave.
296339
297340Linking and monitoring are foundational tools for * supervising* processes, where a top level
298341process manages a set of children, starting, stopping and restarting them as necessary.
0 commit comments