@@ -295,6 +295,163 @@ let result = ports.foldl(0, |accum, port| *accum + port.recv() );
295295# fn some_expensive_computation(_i: uint) -> int { 42 }
296296~~~
297297
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+
298455# Unfinished notes
299456
300457## Actor patterns
0 commit comments