Skip to content

Remove linked failure from the runtime #10603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 25, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions doc/rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -2350,9 +2350,9 @@ Indices are zero-based, and may be of any integral type. Vector access
is bounds-checked at run-time. When the check fails, it will put the
task in a _failing state_.

~~~~
~~~~ {.xfail-test}
# use std::task;
# do task::spawn_unlinked {
# do task::spawn {

([1, 2, 3, 4])[0];
(["a", "b"])[10]; // fails
Expand Down
112 changes: 1 addition & 111 deletions doc/tutorial-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,22 +402,6 @@ freeing memory along the way---and then exits. Unlike exceptions in C++,
exceptions in Rust are unrecoverable within a single task: once a task fails,
there is no way to "catch" the exception.

All tasks are, by default, _linked_ to each other. That means that the fates
of all tasks are intertwined: if one fails, so do all the others.

~~~{.xfail-test .linked-failure}
# use std::task::spawn;
# use std::task;
# fn do_some_work() { loop { task::yield() } }
# do task::try {
// Create a child task that fails
do spawn { fail!() }

// This will also fail because the task we spawned failed
do_some_work();
# };
~~~

While it isn't possible for a task to recover from failure, tasks may notify
each other of failure. The simplest way of handling task failure is with the
`try` function, which is similar to `spawn`, but immediately blocks waiting
Expand Down Expand Up @@ -464,101 +448,7 @@ it trips, indicates an unrecoverable logic error); in other cases you
might want to contain the failure at a certain boundary (perhaps a
small piece of input from the outside world, which you happen to be
processing in parallel, is malformed and its processing task can't
proceed). Hence, you will need different _linked failure modes_.

## Failure modes

By default, task failure is _bidirectionally linked_, which means that if
either task fails, it kills the other one.

~~~{.xfail-test .linked-failure}
# use std::task;
# use std::comm::oneshot;
# fn sleep_forever() { loop { let (p, c) = oneshot::<()>(); p.recv(); } }
# do task::try {
do spawn {
do spawn {
fail!(); // All three tasks will fail.
}
sleep_forever(); // Will get woken up by force, then fail
}
sleep_forever(); // Will get woken up by force, then fail
# };
~~~

If you want parent tasks to be able to kill their children, but do not want a
parent to fail automatically if one of its child task fails, you can call
`task::spawn_supervised` for _unidirectionally linked_ failure. The
function `task::try`, which we saw previously, uses `spawn_supervised`
internally, with additional logic to wait for the child task to finish
before returning. Hence:

~~~{.xfail-test .linked-failure}
# use std::comm::{stream, Chan, Port};
# use std::comm::oneshot;
# use std::task::{spawn, try};
# use std::task;
# fn sleep_forever() { loop { let (p, c) = oneshot::<()>(); p.recv(); } }
# do task::try {
let (receiver, sender): (Port<int>, Chan<int>) = stream();
do spawn { // Bidirectionally linked
// Wait for the supervised child task to exist.
let message = receiver.recv();
// Kill both it and the parent task.
assert!(message != 42);
}
do try { // Unidirectionally linked
sender.send(42);
sleep_forever(); // Will get woken up by force
}
// Flow never reaches here -- parent task was killed too.
# };
~~~

Supervised failure is useful in any situation where one task manages
multiple fallible child tasks, and the parent task can recover
if any child fails. On the other hand, if the _parent_ (supervisor) fails,
then there is nothing the children can do to recover, so they should
also fail.

Supervised task failure propagates across multiple generations even if
an intermediate generation has already exited:

~~~{.xfail-test .linked-failure}
# use std::task;
# use std::comm::oneshot;
# fn sleep_forever() { loop { let (p, c) = oneshot::<()>(); p.recv(); } }
# fn wait_for_a_while() { for _ in range(0, 1000u) { task::yield() } }
# do task::try::<int> {
do task::spawn_supervised {
do task::spawn_supervised {
sleep_forever(); // Will get woken up by force, then fail
}
// Intermediate task immediately exits
}
wait_for_a_while();
fail!(); // Will kill grandchild even if child has already exited
# };
~~~

Finally, tasks can be configured to not propagate failure to each
other at all, using `task::spawn_unlinked` for _isolated failure_.

~~~{.xfail-test .linked-failure}
# use std::task;
# fn random() -> uint { 100 }
# fn sleep_for(i: uint) { for _ in range(0, i) { task::yield() } }
# do task::try::<()> {
let (time1, time2) = (random(), random());
do task::spawn_unlinked {
sleep_for(time2); // Won't get forced awake
fail!();
}
sleep_for(time1); // Won't get forced awake
fail!();
// It will take MAX(time1,time2) for the program to finish.
# };
~~~
proceed).

## Creating a task with a bi-directional communication path

Expand Down
2 changes: 1 addition & 1 deletion src/libextra/arc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ mod tests {
let arc2 = ~arc.clone();
let (p, c) = comm::stream();

do task::spawn_unlinked || {
do spawn {
let _ = p.recv();
do arc2.access_cond |one, cond| {
cond.signal();
Expand Down
7 changes: 3 additions & 4 deletions src/libextra/comm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ pub fn rendezvous<T: Send>() -> (SyncPort<T>, SyncChan<T>) {
mod test {
use comm::{DuplexStream, rendezvous};
use std::rt::test::run_in_uv_task;
use std::task::spawn_unlinked;


#[test]
Expand Down Expand Up @@ -177,7 +176,7 @@ mod test {
#[test]
fn send_and_fail_and_try_recv() {
let (port, chan) = rendezvous();
do spawn_unlinked {
do spawn {
chan.duplex_stream.send(()); // Can't access this field outside this module
fail!()
}
Expand All @@ -187,7 +186,7 @@ mod test {
#[test]
fn try_send_and_recv_then_fail_before_ack() {
let (port, chan) = rendezvous();
do spawn_unlinked {
do spawn {
port.duplex_stream.recv();
fail!()
}
Expand All @@ -198,7 +197,7 @@ mod test {
#[should_fail]
fn send_and_recv_then_fail_before_ack() {
let (port, chan) = rendezvous();
do spawn_unlinked {
do spawn {
port.duplex_stream.recv();
fail!()
}
Expand Down
26 changes: 1 addition & 25 deletions src/libextra/future.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

use std::cell::Cell;
use std::comm::{PortOne, oneshot};
use std::task;
use std::util::replace;

/// A type encapsulating the result of a computation which may not be complete
Expand Down Expand Up @@ -130,29 +129,12 @@ impl<A:Send> Future<A> {

let (port, chan) = oneshot();

do task::spawn_with(chan) |chan| {
do spawn {
chan.send(blk());
}

Future::from_port(port)
}

pub fn spawn_with<B: Send>(v: B, blk: proc(B) -> A) -> Future<A> {
/*!
* Create a future from a unique closure taking one argument.
*
* The closure and its argument will be moved into a new task. The
* closure will be run and its result used as the value of the future.
*/

let (port, chan) = oneshot();

do task::spawn_with((v, chan)) |(v, chan)| {
chan.send(blk(v));
}

Future::from_port(port)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -207,12 +189,6 @@ mod test {
assert_eq!(f.get(), ~"bale");
}

#[test]
fn test_spawn_with() {
let mut f = Future::spawn_with(~"gale", |s| { s });
assert_eq!(f.get(), ~"gale");
}

#[test]
#[should_fail]
fn test_futurefail() {
Expand Down
Loading