Skip to content

Commit 7b7c2a4

Browse files
committed
docs: Begin integrating bblum's linked failure blog post
1 parent 84d10c6 commit 7b7c2a4

File tree

1 file changed

+157
-0
lines changed

1 file changed

+157
-0
lines changed

doc/tutorial-tasks.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)