Skip to content

Commit

Permalink
auto merge of #10603 : alexcrichton/rust/no-linked-failure, r=brson
Browse files Browse the repository at this point in the history
The reasons for doing this are:

* The model on which linked failure is based is inherently complex
* The implementation is also very complex, and there are few remaining who
  fully understand the implementation
* There are existing race conditions in the core context switching function of
  the scheduler, and possibly others.
* It's unclear whether this model of linked failure maps well to a 1:1 threading
  model

Linked failure is often a desired aspect of tasks, but we would like to take a
much more conservative approach in re-implementing linked failure if at all.

Closes #8674
Closes #8318
Closes #8863
  • Loading branch information
bors committed Nov 25, 2013
2 parents ca32743 + acca9e3 commit 2cc1e16
Show file tree
Hide file tree
Showing 39 changed files with 403 additions and 2,531 deletions.
4 changes: 2 additions & 2 deletions doc/rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -2349,9 +2349,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

0 comments on commit 2cc1e16

Please sign in to comment.