@@ -3,9 +3,9 @@ layout: documentation
3
3
title : Documentation
4
4
---
5
5
6
- ### Cloud Haskell
6
+ ### Cloud Haskell Platform
7
7
8
- This is [ * Cloud Haskell* ] [ 1 ] . Cloud Haskell is a set of libraries
8
+ This is the [ * Cloud Haskell Platform * ] [ 1 ] . Cloud Haskell is a set of libraries
9
9
that bring Erlang-style concurrency and distribution to Haskell programs. This
10
10
project is an implementation of that distributed computing interface, where
11
11
processes communicate with one another through explicit message passing rather
@@ -238,14 +238,14 @@ types such as `TMVar` just as normal Haskell threads would.
238
238
### Typed Channels
239
239
240
240
Channels provides an alternative to message transmission with ` send ` and ` expect ` .
241
- While ` send ` and ` expect ` allow transmission of messages of any ` Serializable `
241
+ While ` send ` and ` expect ` allow us to transmit messages of any ` Serializable `
242
242
type, channels require a uniform type. Channels work like a distributed equivalent
243
243
of Haskell's ` Control.Concurrent.Chan ` , however they have distinct ends: a single
244
244
receiving port and a corollary send port.
245
245
246
246
Channels provide a nice alternative to * bare send and receive* , which is a bit
247
- * unHaskellish * , because the processes message queue has messages of multiple
248
- types, and we have to do dynamic type checking.
247
+ * un-Haskell-ish * , since our process' message queue can contain messages of multiple
248
+ types, forcing us to undertake dynamic type checking at runtime .
249
249
250
250
We create channels with a call to ` newChan ` , and send/receive on them using the
251
251
` {send,receive}Chan ` primitives:
@@ -264,19 +264,17 @@ channelsDemo = do
264
264
{% endhighlight %}
265
265
266
266
Channels are particularly useful when you are sending a message that needs a
267
- response, because the code that receives the response knows exactly where it
268
- came from - i.e., it knows that it came from the ` SendPort ` connected to
269
- the ` ReceivePort ` on which it just received a response.
267
+ response, because we know exactly where to look for the reply.
270
268
271
- Channels can sometimes allows message types to be simplified, as passing a
272
- ` ProcessId ` to reply to isn't required. Channels are not so useful when you
273
- need to spawn a process and then send a bunch a messages to it and wait for
274
- replies, because we can’t send the ` ReceivePort ` .
269
+ Channels can also allow message types to be simplified, as passing a
270
+ ` ProcessId ` for the reply isn't required. Channels aren't so useful when we
271
+ need to spawn a process and send a bunch a messages to it, then wait for
272
+ replies however; we can’t send a ` ReceivePort ` since it is not ` Serializable ` .
275
273
276
- ReceivePorts can be merged, so you can listen on several simultaneously. In the
277
- latest version of [ distributed-process] [ 2 ] , you can listen for * regular* messages
278
- and on multiple channels at the same time, using ` matchChan ` in the list of
279
- allowed matches passed ` receive ` .
274
+ ` ReceivePort ` s can be merged, so we can listen on several simultaneously. In the
275
+ latest version of [ distributed-process] [ 2 ] , we can listen for * regular* messages
276
+ and multiple channels at the same time, using ` matchChan ` in the list of
277
+ allowed matches passed ` receiveWait ` and ` receiveTimeout ` .
280
278
281
279
### Linking and monitoring
282
280
@@ -289,71 +287,27 @@ a set of children, starting, stopping and restarting them as necessary.
289
287
290
288
### Stopping Processes
291
289
292
- Some processes, like the * outer* process in the previous example, will run until
293
- they've completed and then return their value. This is just as we find with IO action,
294
- and there is an instance of ` MonadIO ` for the ` Process ` monad, so you can ` liftIO ` if
295
- you need to evaluate IO actions.
296
-
297
290
Because processes are implemented with ` forkIO ` we might be tempted to stop
298
291
them by throwing an asynchronous exception to the process, but this is almost
299
- certainly the wrong thing to do. Instead we might send a kind of poison pill,
300
- which the process * ought* to handle by shutting down gracefully. Unfortunately
301
- because of the asynchronous nature of sending, this is no good because ` send `
302
- will not fail under any circumstances. In fact, because ` send ` doesn't block,
303
- we therefore have no way to know if the recipient existed at the time we sent the
304
- poison pill. Even if the recipient did exist, we still have no guarantee that
305
- the message we sent actually arrived - the network connection between the nodes
306
- could have broken, for example. Making this * shutdown* protocol synchronous is
307
- no good either - how long would we wait for a reply? Indefinitely?
308
-
309
- Exit signals come in two flavours - those that can
310
- be caught and those that cannot. A call to
311
- ` exit :: (Serializable a) => ProcessId -> a -> Process () ` will dispatch an
312
- exit signal to the specified process. These * signals* can be intercepted and
313
- handled by the destination process however, so if you need to terminate the
314
- process in a brutal way, you can use the ` kill :: ProcessId -> String -> Process () `
315
- function, which sends an exit signal that cannot be handled.
316
-
317
- ------
318
- #### __ An important note about exit signals__
319
-
320
- Exit signals in Cloud Haskell are unlike asynchronous exceptions in regular
321
- haskell code. Whilst processes * can* use asynchronous exceptions - there's
322
- nothing stoping this since the ` Process ` monad is an instance of ` MonadIO ` -
323
- exceptions thrown are not bound by the same ordering guarantees as messages
324
- delivered to a process. Link failures and exit signals * might* be implemented
325
- using asynchronous exceptions - that is the case in the current
326
- implementation - but these are implemented in such a fashion that if you
327
- send a message and * then* an exit signal, the message is guaranteed to arrive
328
- first.
329
-
330
- You should avoid throwing your own exceptions in code where possible. Instead,
331
- you should terminate yourself, or another process, using the built-in primitives
332
- ` exit ` , ` kill ` and ` die ` .
333
-
334
- {% highlight haskell %}
335
- exit pid reason -- force ` pid ` to exit - reason can be any ` Serializable ` message
336
- kill pid reason -- reason is a string - the * kill* signal cannot be caught
337
- die reason -- as 'exit' but kills * us*
338
- {% endhighlight %}
339
-
340
- The ` exit ` and ` kill ` primitives do essentially the same thing, but catching
341
- the specific exception thrown by ` kill ` is impossible, making ` kill ` an
342
- * untrappable exit signal* . Of course you could trap ** all** exceptions, but
343
- you already know that's a very bad idea right!?
344
-
345
- The ` exit ` primitive is a little different. This provides support for trapping
346
- exit signals in a generic way, so long as your * exit handler* is able to
347
- recognise the underlying type of the 'exit reason'. This (reason for exiting)
348
- is stored as a raw ` Message ` , so if your handler takes the appropriate type
349
- as an input (and therefore the ` Message ` can be decoded and passed to the
350
- handler) then the handler will run. This is pretty much the same approach as
351
- exception handling using ` Typeable ` , except that we decide whether or not the
352
- exception can be handled based on the type of ` reason ` instead of the type of
353
- the exception itself.
354
-
355
- Calling ` die ` will immediately raise an exit signal (i.e., ` ProcessExitException ` )
356
- 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.
357
311
358
312
----
359
313
@@ -373,7 +327,7 @@ The [distributed-process-platform][18] library implements parts of the
373
327
in the original paper and implemented by the [ remote] [ 14 ] package. In particular,
374
328
we diverge from the original design and defer to many of the principles
375
329
defined by Erlang's [ Open Telecom Platform] [ 13 ] , taking in some well established
376
- Haskell concurrency design patterns alongside .
330
+ Haskell concurrency design patterns along the way .
377
331
378
332
In fact, [ distributed-process-platform] [ 18 ] does not really consider the
379
333
* task layer* in great detail. We provide an API comparable to remote's
@@ -465,6 +419,12 @@ The API for `Async` is fairly rich, so reading the haddocks is suggested.
465
419
466
420
#### Managed Processes
467
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
+
468
428
Looking at * typed channels* , we noted that their insistence on a specific input
469
429
domain was more * haskell-ish* than working with bare send and receive primitives.
470
430
The ` Async ` sub-package also provides a type safe interface for receiving data,
@@ -473,12 +433,12 @@ although it is limited to running a computation and waiting for its result.
473
433
The [ Control.Distributed.Processes.Platform.ManagedProcess] [ 21 ] API provides a
474
434
number of different abstractions that can be used to achieve similar benefits
475
435
in your code. It works by introducing a standard protocol between your process
476
- and the * world around * , which governs how to handle request/reply processing,
477
- 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
478
438
hooks that terminating processes can use to clean up residual state.
479
439
480
440
The [ API documentation] [ 21 ] is quite extensive, so here we will simply point
481
- out the obvious differences. A implemented implemented with ` ManagedProcess `
441
+ out the obvious differences. A process implemented with ` ManagedProcess `
482
442
can present a type safe API to its callers (and the server side code too!),
483
443
although that's not its primary benefit. For a very simplified example:
484
444
@@ -516,6 +476,18 @@ just provides callback functions which take some state and either return a
516
476
new state and a reply, or just a new state. The process is * managed* in the
517
477
sense that its mailbox is under someone else's control.
518
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
+
519
491
More complex examples of the ` ManagedProcess ` API can be seen in the
520
492
[ Managed Processes tutorial] [ 22 ] . API documentation for HEAD is available
521
493
[ here] [ 21 ] .
0 commit comments