@@ -287,71 +287,27 @@ a set of children, starting, stopping and restarting them as necessary.
287287
288288### Stopping Processes
289289
290- Some processes, like the * outer* process in the previous example, will run until
291- they've completed and then return their value. This is just as we find with IO action,
292- and there is an instance of ` MonadIO ` for the ` Process ` monad, so you can ` liftIO ` if
293- you need to evaluate IO actions.
294-
295290Because processes are implemented with ` forkIO ` we might be tempted to stop
296291them by throwing an asynchronous exception to the process, but this is almost
297- certainly the wrong thing to do. Instead we might send a kind of poison pill,
298- which the process * ought* to handle by shutting down gracefully. Unfortunately
299- because of the asynchronous nature of sending, this is no good because ` send `
300- will not fail under any circumstances. In fact, because ` send ` doesn't block,
301- we therefore have no way to know if the recipient existed at the time we sent the
302- poison pill. Even if the recipient did exist, we still have no guarantee that
303- the message we sent actually arrived - the network connection between the nodes
304- could have broken, for example. Making this * shutdown* protocol synchronous is
305- no good either - how long would we wait for a reply? Indefinitely?
306-
307- Exit signals come in two flavours - those that can
308- be caught and those that cannot. A call to
309- ` exit :: (Serializable a) => ProcessId -> a -> Process () ` will dispatch an
310- exit signal to the specified process. These * signals* can be intercepted and
311- handled by the destination process however, so if you need to terminate the
312- process in a brutal way, you can use the ` kill :: ProcessId -> String -> Process () `
313- function, which sends an exit signal that cannot be handled.
314-
315- ------
316- #### __ An important note about exit signals__
317-
318- Exit signals in Cloud Haskell are unlike asynchronous exceptions in regular
319- haskell code. Whilst a process * can* use asynchronous exceptions - there's
320- nothing stoping this since the ` Process ` monad is an instance of ` MonadIO ` -
321- exceptions thrown are not bound by the same ordering guarantees as messages
322- delivered to a process. Link failures and exit signals * might* be implemented
323- using asynchronous exceptions - that is the case in the current
324- implementation - but these are implemented in such a fashion that if you
325- send a message and * then* an exit signal, the message is guaranteed to arrive
326- first.
327-
328- You should avoid throwing your own exceptions in code where possible. Instead,
329- you should terminate yourself, or another process, using the built-in primitives
330- ` exit ` , ` kill ` and ` die ` .
331-
332- {% highlight haskell %}
333- exit pid reason -- force ` pid ` to exit - reason can be any ` Serializable ` message
334- kill pid reason -- reason is a string - the * kill* signal cannot be caught
335- die reason -- as 'exit' but kills * us*
336- {% endhighlight %}
337-
338- The ` exit ` and ` kill ` primitives do essentially the same thing, but catching
339- the specific exception thrown by ` kill ` is impossible, making ` kill ` an
340- * untrappable exit signal* . Of course you could trap ** all** exceptions, but
341- you already know that's a very bad idea right!?
342-
343- The ` exit ` primitive is a little different. This provides support for trapping
344- exit signals in a generic way, so long as your * exit handler* is able to
345- recognise the underlying type of the 'exit reason'. This (reason for exiting)
346- is stored as a raw ` Message ` , so if your handler takes the appropriate type
347- as an input (and therefore the ` Message ` can be decoded and passed to the
348- handler) then the handler will run. This is pretty much the same approach as
349- exception handling using ` Typeable ` , except that we decide whether or not the
350- exception can be handled based on the type of ` reason ` instead of the type of
351- the exception itself.
352-
353- Calling ` die ` will immediately raise an exit signal (i.e., ` ProcessExitException ` )
354- in the calling process.
292+ certainly the wrong thing to do. Firstly, processes might reside on a remote
293+ node, in which case throwing an exception is impossible. Secondly, if we send
294+ some messages to a process' mailbox and then dispatch an exception to kill it,
295+ there is no guarantee that the subject will receive our message before being
296+ terminated by the asynchronous exception.
297+
298+ To terminate a process unconditionally, we use the ` kill ` primitive, which
299+ dispatches an asynchronous exception (killing the subject) safely, respecting
300+ remote calls to processes on disparate nodes and observing message ordering
301+ guarantees such that ` send pid "hello" >> kill pid "goodbye" ` behaves quite
302+ unsurprisingly, delivering the message before the kill signal.
303+
304+ Exit signals come in two flavours however - those that can be caught and those
305+ that cannot. Whilst a call to ` kill ` results in an _ un-trappable_ exception,
306+ a call to ` exit :: (Serializable a) => ProcessId -> a -> Process () ` will dispatch
307+ an exit signal to the specified process that can be caught. These * signals* are
308+ intercepted and handled by the destination process using ` catchExit ` , allowing
309+ the receiver to match on the ` Serializable ` datum tucked away in the * exit signal*
310+ and decide whether to oblige or not.
355311
356312----
357313
@@ -463,6 +419,12 @@ The API for `Async` is fairly rich, so reading the haddocks is suggested.
463419
464420#### Managed Processes
465421
422+ The main idea behind a ` ManagedProcess ` is to separate the functional
423+ and non-functional aspects of an actor. By functional, we mean whatever
424+ application specific task the actor performs, and by non-functional
425+ we mean the * concurrency* or, more precisely, handling of the process'
426+ mailbox and its interaction with other actors (i.e., clients).
427+
466428Looking at * typed channels* , we noted that their insistence on a specific input
467429domain was more * haskell-ish* than working with bare send and receive primitives.
468430The ` Async ` sub-package also provides a type safe interface for receiving data,
@@ -471,12 +433,12 @@ although it is limited to running a computation and waiting for its result.
471433The [ Control.Distributed.Processes.Platform.ManagedProcess] [ 21 ] API provides a
472434number of different abstractions that can be used to achieve similar benefits
473435in your code. It works by introducing a standard protocol between your process
474- and the * world around * , which governs how to handle request/reply processing,
475- exit signals, timeouts, sleep /hibernation with ` threadDelay ` and even provides
436+ and the * world outside * , which governs how to handle request/reply processing,
437+ exit signals, timeouts, sleeping /hibernation with ` threadDelay ` and even provides
476438hooks that terminating processes can use to clean up residual state.
477439
478440The [ API documentation] [ 21 ] is quite extensive, so here we will simply point
479- out the obvious differences. A implemented implemented with ` ManagedProcess `
441+ out the obvious differences. A process implemented with ` ManagedProcess `
480442can present a type safe API to its callers (and the server side code too!),
481443although that's not its primary benefit. For a very simplified example:
482444
@@ -514,6 +476,18 @@ just provides callback functions which take some state and either return a
514476new state and a reply, or just a new state. The process is * managed* in the
515477sense that its mailbox is under someone else's control.
516478
479+ A NOTE ABOUT THE CALL API AND THAT IT WILL FAIL (WITH UNHANDLED MESSAGE) IF
480+ THE CALLER IS EXPECTING A TYPE THAT DIFFERS FROM THE ONE THE SERVER PLANS
481+ TO RETURN, SINCE THE RETURN TYPE IS ENCODED IN THE CALL-MESSAGE TYPE ITSELF.
482+
483+ TODO: WRITE A TEST TO PROVE THE ABOVE
484+
485+ TODO: ADD AN API BASED ON SESSION TYPES AS A KIND OF MANAGED PROCESS.....
486+
487+ In a forthcoming tutorial, we'll look at the ` Control.Distributed.Process.Platform.Task `
488+ API, which looks a lot like ` Async ` but manages exit signals in a single thread and makes
489+ configurable task pools and task supervision strategy part of its API.
490+
517491More complex examples of the ` ManagedProcess ` API can be seen in the
518492[ Managed Processes tutorial] [ 22 ] . API documentation for HEAD is available
519493[ here] [ 21 ] .
0 commit comments