@@ -295,6 +295,163 @@ let result = ports.foldl(0, |accum, port| *accum + port.recv() );
295
295
# fn some_expensive_computation(_i: uint) -> int { 42 }
296
296
~~~
297
297
298
+ # TODO
299
+
300
+ # Handling task failure
301
+
302
+ Rust has a built-in mechanism for raising exceptions, written ` fail `
303
+ (or ` fail ~"reason" ` , or sometimes ` assert expr ` ), and it causes the
304
+ task to unwind its stack, running destructors and freeing memory along
305
+ the way, and then exit itself. Unlike C++, exceptions in Rust are
306
+ unrecoverable within a single task - once a task fails there is no way
307
+ to "catch" the exception.
308
+
309
+ All tasks are, by default, _ linked_ to each other, meaning their fate
310
+ is interwined, and if one fails so do all of them.
311
+
312
+ ~~~
313
+ # use task::spawn;
314
+ # fn do_some_work() { loop { task::yield() } }
315
+ # do task::try {
316
+ // Create a child task that fails
317
+ do spawn { fail }
318
+
319
+ // This will also fail because the task we spawned failed
320
+ do_some_work();
321
+ # };
322
+ ~~~
323
+
324
+ While it isn't possible for a task to recover from failure,
325
+ tasks may be notified when _ other_ tasks fail. The simplest way
326
+ of handling task failure is with the ` try ` function, which is
327
+ similar to spawn, but immediately blocks waiting for the child
328
+ task to finish.
329
+
330
+ ~~~
331
+ # fn some_condition() -> bool { false }
332
+ # fn calculate_result() -> int { 0 }
333
+ let result: Result<int, ()> = do task::try {
334
+ if some_condition() {
335
+ calculate_result()
336
+ } else {
337
+ fail ~"oops!";
338
+ }
339
+ };
340
+ assert result.is_err();
341
+ ~~~
342
+
343
+ Unlike ` spawn ` , the function spawned using ` try ` may return a value,
344
+ which ` try ` will dutifully propagate back to the caller in a [ ` Result ` ]
345
+ enum. If the child task terminates successfully, ` try ` will
346
+ return an ` Ok ` result; if the child task fails, ` try ` will return
347
+ an ` Error ` result.
348
+
349
+ [ `Result` ] : core/result.html
350
+
351
+ > *** Note:*** A failed task does not currently produce a useful error
352
+ > value (all error results from ` try ` are equal to ` Err(()) ` ). In the
353
+ > future it may be possible for tasks to intercept the value passed to
354
+ > ` fail ` .
355
+
356
+ TODO: Need discussion of ` future_result ` in order to make failure
357
+ modes useful.
358
+
359
+ But not all failure is created equal. In some cases you might need to
360
+ abort the entire program (perhaps you're writing an assert which, if
361
+ it trips, indicates an unrecoverable logic error); in other cases you
362
+ might want to contain the failure at a certain boundary (perhaps a
363
+ small piece of input from the outside world, which you happen to be
364
+ processing in parallel, is malformed and its processing task can't
365
+ proceed). Hence the need for different _ linked failure modes_ .
366
+
367
+ ## Failure modes
368
+
369
+ By default, task failure is _ bidirectionally linked_ , which means if
370
+ either task dies, it kills the other one.
371
+
372
+ ~~~
373
+ # fn sleep_forever() { loop { task::yield() } }
374
+ # do task::try {
375
+ do task::spawn {
376
+ do task::spawn {
377
+ fail; // All three tasks will die.
378
+ }
379
+ sleep_forever(); // Will get woken up by force, then fail
380
+ }
381
+ sleep_forever(); // Will get woken up by force, then fail
382
+ # };
383
+ ~~~
384
+
385
+ If you want parent tasks to kill their children, but not for a child
386
+ task's failure to kill the parent, you can call
387
+ ` task::spawn_supervised ` for _ unidirectionally linked_ failure. The
388
+ function ` task::try ` , which we saw previously, uses ` spawn_supervised `
389
+ internally, with additional logic to wait for the child task to finish
390
+ before returning. Hence:
391
+
392
+ ~~~
393
+ # use pipes::{stream, Chan, Port};
394
+ # use task::{spawn, try};
395
+ # fn sleep_forever() { loop { task::yield() } }
396
+ # do task::try {
397
+ let (sender, receiver): (Chan<int>, Port<int>) = stream();
398
+ do spawn { // Bidirectionally linked
399
+ // Wait for the supervised child task to exist.
400
+ let message = receiver.recv();
401
+ // Kill both it and the parent task.
402
+ assert message != 42;
403
+ }
404
+ do try { // Unidirectionally linked
405
+ sender.send(42);
406
+ sleep_forever(); // Will get woken up by force
407
+ }
408
+ // Flow never reaches here -- parent task was killed too.
409
+ # };
410
+ ~~~
411
+
412
+ Supervised failure is useful in any situation where one task manages
413
+ multiple fallible child tasks, and the parent task can recover
414
+ if any child files. On the other hand, if the _ parent_ (supervisor) fails
415
+ then there is nothing the children can do to recover, so they should
416
+ also fail.
417
+
418
+ Supervised task failure propagates across multiple generations even if
419
+ an intermediate generation has already exited:
420
+
421
+ ~~~
422
+ # fn sleep_forever() { loop { task::yield() } }
423
+ # fn wait_for_a_while() { for 1000.times { task::yield() } }
424
+ # do task::try::<int> {
425
+ do task::spawn_supervised {
426
+ do task::spawn_supervised {
427
+ sleep_forever(); // Will get woken up by force, then fail
428
+ }
429
+ // Intermediate task immediately exits
430
+ }
431
+ wait_for_a_while();
432
+ fail; // Will kill grandchild even if child has already exited
433
+ # };
434
+ ~~~
435
+
436
+ Finally, tasks can be configured to not propagate failure to each
437
+ other at all, using ` task::spawn_unlinked ` for _ isolated failure_ .
438
+
439
+ ~~~
440
+ # fn random() -> int { 100 }
441
+ # fn sleep_for(i: int) { for i.times { task::yield() } }
442
+ # do task::try::<()> {
443
+ let (time1, time2) = (random(), random());
444
+ do task::spawn_unlinked {
445
+ sleep_for(time2); // Won't get forced awake
446
+ fail;
447
+ }
448
+ sleep_for(time1); // Won't get forced awake
449
+ fail;
450
+ // It will take MAX(time1,time2) for the program to finish.
451
+ # };
452
+ ~~~
453
+
454
+
298
455
# Unfinished notes
299
456
300
457
## Actor patterns
0 commit comments