Skip to content

Commit 894ce43

Browse files
committed
---
yaml --- r: 30935 b: refs/heads/incoming c: 7b7c2a4 h: refs/heads/master i: 30933: 06bdf92 30931: 446e75b 30927: 3343e7e v: v3
1 parent a0833a4 commit 894ce43

File tree

2 files changed

+158
-1
lines changed

2 files changed

+158
-1
lines changed

[refs]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ refs/heads/try: d324a424d8f84b1eb049b12cf34182bda91b0024
66
refs/tags/release-0.1: 1f5c5126e96c79d22cb7862f75304136e204f105
77
refs/heads/ndm: f3868061cd7988080c30d6d5bf352a5a5fe2460b
88
refs/heads/try2: d0c6ce338884ee21843f4b40bf6bf18d222ce5df
9-
refs/heads/incoming: 84d10c68fb893d32f0a2bf4d4f7eb05511023776
9+
refs/heads/incoming: 7b7c2a49b92702ea2553eeb27c2ab4a38931db63
1010
refs/heads/dist-snap: 2f32a1581f522e524009138b33b1c7049ced668d
1111
refs/tags/release-0.2: c870d2dffb391e14efb05aa27898f1f6333a9596
1212
refs/tags/release-0.3: b5f0d0f648d9a6153664837026ba1be43d3e2503

branches/incoming/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)