@@ -287,71 +287,27 @@ a set of children, starting, stopping and restarting them as necessary.
287
287
288
288
### Stopping Processes
289
289
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
-
295
290
Because processes are implemented with ` forkIO ` we might be tempted to stop
296
291
them 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.
355
311
356
312
----
357
313
@@ -463,6 +419,12 @@ The API for `Async` is fairly rich, so reading the haddocks is suggested.
463
419
464
420
#### Managed Processes
465
421
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
+
466
428
Looking at * typed channels* , we noted that their insistence on a specific input
467
429
domain was more * haskell-ish* than working with bare send and receive primitives.
468
430
The ` 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.
471
433
The [ Control.Distributed.Processes.Platform.ManagedProcess] [ 21 ] API provides a
472
434
number of different abstractions that can be used to achieve similar benefits
473
435
in 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
476
438
hooks that terminating processes can use to clean up residual state.
477
439
478
440
The [ 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 `
480
442
can present a type safe API to its callers (and the server side code too!),
481
443
although that's not its primary benefit. For a very simplified example:
482
444
@@ -514,6 +476,18 @@ just provides callback functions which take some state and either return a
514
476
new state and a reply, or just a new state. The process is * managed* in the
515
477
sense that its mailbox is under someone else's control.
516
478
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
+
517
491
More complex examples of the ` ManagedProcess ` API can be seen in the
518
492
[ Managed Processes tutorial] [ 22 ] . API documentation for HEAD is available
519
493
[ here] [ 21 ] .
0 commit comments