mirror of
https://github.com/rust-lang/rust.git
synced 2024-10-30 22:12:15 +00:00
Remove linked failure from the runtime
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
This commit is contained in:
parent
85a1eff3a9
commit
acca9e3834
@ -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
|
is bounds-checked at run-time. When the check fails, it will put the
|
||||||
task in a _failing state_.
|
task in a _failing state_.
|
||||||
|
|
||||||
~~~~
|
~~~~ {.xfail-test}
|
||||||
# use std::task;
|
# use std::task;
|
||||||
# do task::spawn_unlinked {
|
# do task::spawn {
|
||||||
|
|
||||||
([1, 2, 3, 4])[0];
|
([1, 2, 3, 4])[0];
|
||||||
(["a", "b"])[10]; // fails
|
(["a", "b"])[10]; // fails
|
||||||
|
@ -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,
|
exceptions in Rust are unrecoverable within a single task: once a task fails,
|
||||||
there is no way to "catch" the exception.
|
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
|
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
|
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
|
`try` function, which is similar to `spawn`, but immediately blocks waiting
|
||||||
@ -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
|
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
|
small piece of input from the outside world, which you happen to be
|
||||||
processing in parallel, is malformed and its processing task can't
|
processing in parallel, is malformed and its processing task can't
|
||||||
proceed). Hence, you will need different _linked failure modes_.
|
proceed).
|
||||||
|
|
||||||
## 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.
|
|
||||||
# };
|
|
||||||
~~~
|
|
||||||
|
|
||||||
## Creating a task with a bi-directional communication path
|
## Creating a task with a bi-directional communication path
|
||||||
|
|
||||||
|
@ -655,7 +655,7 @@ mod tests {
|
|||||||
let arc2 = ~arc.clone();
|
let arc2 = ~arc.clone();
|
||||||
let (p, c) = comm::stream();
|
let (p, c) = comm::stream();
|
||||||
|
|
||||||
do task::spawn_unlinked || {
|
do spawn {
|
||||||
let _ = p.recv();
|
let _ = p.recv();
|
||||||
do arc2.access_cond |one, cond| {
|
do arc2.access_cond |one, cond| {
|
||||||
cond.signal();
|
cond.signal();
|
||||||
|
@ -137,7 +137,6 @@ pub fn rendezvous<T: Send>() -> (SyncPort<T>, SyncChan<T>) {
|
|||||||
mod test {
|
mod test {
|
||||||
use comm::{DuplexStream, rendezvous};
|
use comm::{DuplexStream, rendezvous};
|
||||||
use std::rt::test::run_in_uv_task;
|
use std::rt::test::run_in_uv_task;
|
||||||
use std::task::spawn_unlinked;
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -177,7 +176,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn send_and_fail_and_try_recv() {
|
fn send_and_fail_and_try_recv() {
|
||||||
let (port, chan) = rendezvous();
|
let (port, chan) = rendezvous();
|
||||||
do spawn_unlinked {
|
do spawn {
|
||||||
chan.duplex_stream.send(()); // Can't access this field outside this module
|
chan.duplex_stream.send(()); // Can't access this field outside this module
|
||||||
fail!()
|
fail!()
|
||||||
}
|
}
|
||||||
@ -187,7 +186,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn try_send_and_recv_then_fail_before_ack() {
|
fn try_send_and_recv_then_fail_before_ack() {
|
||||||
let (port, chan) = rendezvous();
|
let (port, chan) = rendezvous();
|
||||||
do spawn_unlinked {
|
do spawn {
|
||||||
port.duplex_stream.recv();
|
port.duplex_stream.recv();
|
||||||
fail!()
|
fail!()
|
||||||
}
|
}
|
||||||
@ -198,7 +197,7 @@ mod test {
|
|||||||
#[should_fail]
|
#[should_fail]
|
||||||
fn send_and_recv_then_fail_before_ack() {
|
fn send_and_recv_then_fail_before_ack() {
|
||||||
let (port, chan) = rendezvous();
|
let (port, chan) = rendezvous();
|
||||||
do spawn_unlinked {
|
do spawn {
|
||||||
port.duplex_stream.recv();
|
port.duplex_stream.recv();
|
||||||
fail!()
|
fail!()
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::comm::{PortOne, oneshot};
|
use std::comm::{PortOne, oneshot};
|
||||||
use std::task;
|
|
||||||
use std::util::replace;
|
use std::util::replace;
|
||||||
|
|
||||||
/// A type encapsulating the result of a computation which may not be complete
|
/// A type encapsulating the result of a computation which may not be complete
|
||||||
@ -130,29 +129,12 @@ impl<A:Send> Future<A> {
|
|||||||
|
|
||||||
let (port, chan) = oneshot();
|
let (port, chan) = oneshot();
|
||||||
|
|
||||||
do task::spawn_with(chan) |chan| {
|
do spawn {
|
||||||
chan.send(blk());
|
chan.send(blk());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future::from_port(port)
|
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)]
|
#[cfg(test)]
|
||||||
@ -207,12 +189,6 @@ mod test {
|
|||||||
assert_eq!(f.get(), ~"bale");
|
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]
|
#[test]
|
||||||
#[should_fail]
|
#[should_fail]
|
||||||
fn test_futurefail() {
|
fn test_futurefail() {
|
||||||
|
@ -22,7 +22,6 @@ use std::borrow;
|
|||||||
use std::comm;
|
use std::comm;
|
||||||
use std::comm::SendDeferred;
|
use std::comm::SendDeferred;
|
||||||
use std::comm::{GenericPort, Peekable};
|
use std::comm::{GenericPort, Peekable};
|
||||||
use std::task;
|
|
||||||
use std::unstable::sync::{Exclusive, UnsafeArc};
|
use std::unstable::sync::{Exclusive, UnsafeArc};
|
||||||
use std::unstable::atomics;
|
use std::unstable::atomics;
|
||||||
use std::unstable::finally::Finally;
|
use std::unstable::finally::Finally;
|
||||||
@ -134,15 +133,13 @@ impl<Q:Send> Sem<Q> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn access<U>(&self, blk: || -> U) -> U {
|
pub fn access<U>(&self, blk: || -> U) -> U {
|
||||||
do task::unkillable {
|
|
||||||
do (|| {
|
do (|| {
|
||||||
self.acquire();
|
self.acquire();
|
||||||
do task::rekillable { blk() }
|
blk()
|
||||||
}).finally {
|
}).finally {
|
||||||
self.release();
|
self.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@ -206,7 +203,6 @@ impl<'self> Condvar<'self> {
|
|||||||
pub fn wait_on(&self, condvar_id: uint) {
|
pub fn wait_on(&self, condvar_id: uint) {
|
||||||
let mut WaitEnd = None;
|
let mut WaitEnd = None;
|
||||||
let mut out_of_bounds = None;
|
let mut out_of_bounds = None;
|
||||||
do task::unkillable {
|
|
||||||
// Release lock, 'atomically' enqueuing ourselves in so doing.
|
// Release lock, 'atomically' enqueuing ourselves in so doing.
|
||||||
unsafe {
|
unsafe {
|
||||||
do (**self.sem).with |state| {
|
do (**self.sem).with |state| {
|
||||||
@ -226,20 +222,15 @@ impl<'self> Condvar<'self> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If deschedule checks start getting inserted anywhere, we can be
|
// If deschedule checks start getting inserted anywhere, we can be
|
||||||
// killed before or after enqueueing. Deciding whether to
|
// killed before or after enqueueing.
|
||||||
// unkillably reacquire the lock needs to happen atomically
|
|
||||||
// wrt enqueuing.
|
|
||||||
do check_cvar_bounds(out_of_bounds, condvar_id, "cond.wait_on()") {
|
do check_cvar_bounds(out_of_bounds, condvar_id, "cond.wait_on()") {
|
||||||
// Unconditionally "block". (Might not actually block if a
|
// Unconditionally "block". (Might not actually block if a
|
||||||
// signaller already sent -- I mean 'unconditionally' in contrast
|
// signaller already sent -- I mean 'unconditionally' in contrast
|
||||||
// with acquire().)
|
// with acquire().)
|
||||||
do (|| {
|
do (|| {
|
||||||
do task::rekillable {
|
|
||||||
let _ = WaitEnd.take_unwrap().recv();
|
let _ = WaitEnd.take_unwrap().recv();
|
||||||
}
|
|
||||||
}).finally {
|
}).finally {
|
||||||
// Reacquire the condvar. Note this is back in the unkillable
|
// Reacquire the condvar.
|
||||||
// section; it needs to succeed, instead of itself dying.
|
|
||||||
match self.order {
|
match self.order {
|
||||||
Just(lock) => do lock.access {
|
Just(lock) => do lock.access {
|
||||||
self.sem.acquire();
|
self.sem.acquire();
|
||||||
@ -251,7 +242,6 @@ impl<'self> Condvar<'self> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Wake up a blocked task. Returns false if there was no blocked task.
|
/// Wake up a blocked task. Returns false if there was no blocked task.
|
||||||
pub fn signal(&self) -> bool { self.signal_on(0) }
|
pub fn signal(&self) -> bool { self.signal_on(0) }
|
||||||
@ -484,7 +474,6 @@ impl RWLock {
|
|||||||
*/
|
*/
|
||||||
pub fn read<U>(&self, blk: || -> U) -> U {
|
pub fn read<U>(&self, blk: || -> U) -> U {
|
||||||
unsafe {
|
unsafe {
|
||||||
do task::unkillable {
|
|
||||||
do (&self.order_lock).access {
|
do (&self.order_lock).access {
|
||||||
let state = &mut *self.state.get();
|
let state = &mut *self.state.get();
|
||||||
let old_count = state.read_count.fetch_add(1, atomics::Acquire);
|
let old_count = state.read_count.fetch_add(1, atomics::Acquire);
|
||||||
@ -494,7 +483,7 @@ impl RWLock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
do (|| {
|
do (|| {
|
||||||
do task::rekillable { blk() }
|
blk()
|
||||||
}).finally {
|
}).finally {
|
||||||
let state = &mut *self.state.get();
|
let state = &mut *self.state.get();
|
||||||
assert!(state.read_mode);
|
assert!(state.read_mode);
|
||||||
@ -511,23 +500,18 @@ impl RWLock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a function with the rwlock in write mode. No calls to 'read' or
|
* Run a function with the rwlock in write mode. No calls to 'read' or
|
||||||
* 'write' from other tasks will run concurrently with this one.
|
* 'write' from other tasks will run concurrently with this one.
|
||||||
*/
|
*/
|
||||||
pub fn write<U>(&self, blk: || -> U) -> U {
|
pub fn write<U>(&self, blk: || -> U) -> U {
|
||||||
do task::unkillable {
|
|
||||||
(&self.order_lock).acquire();
|
(&self.order_lock).acquire();
|
||||||
do (&self.access_lock).access {
|
do (&self.access_lock).access {
|
||||||
(&self.order_lock).release();
|
(&self.order_lock).release();
|
||||||
do task::rekillable {
|
|
||||||
blk()
|
blk()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As write(), but also with a handle to a condvar. Waiting on this
|
* As write(), but also with a handle to a condvar. Waiting on this
|
||||||
@ -562,18 +546,14 @@ impl RWLock {
|
|||||||
// which can't happen until T2 finishes the downgrade-read entirely.
|
// which can't happen until T2 finishes the downgrade-read entirely.
|
||||||
// The astute reader will also note that making waking writers use the
|
// The astute reader will also note that making waking writers use the
|
||||||
// order_lock is better for not starving readers.
|
// order_lock is better for not starving readers.
|
||||||
do task::unkillable {
|
|
||||||
(&self.order_lock).acquire();
|
(&self.order_lock).acquire();
|
||||||
do (&self.access_lock).access_cond |cond| {
|
do (&self.access_lock).access_cond |cond| {
|
||||||
(&self.order_lock).release();
|
(&self.order_lock).release();
|
||||||
do task::rekillable {
|
|
||||||
let opt_lock = Just(&self.order_lock);
|
let opt_lock = Just(&self.order_lock);
|
||||||
blk(&Condvar { sem: cond.sem, order: opt_lock,
|
blk(&Condvar { sem: cond.sem, order: opt_lock,
|
||||||
token: NonCopyable })
|
token: NonCopyable })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As write(), but with the ability to atomically 'downgrade' the lock;
|
* As write(), but with the ability to atomically 'downgrade' the lock;
|
||||||
@ -599,14 +579,11 @@ impl RWLock {
|
|||||||
pub fn write_downgrade<U>(&self, blk: |v: RWLockWriteMode| -> U) -> U {
|
pub fn write_downgrade<U>(&self, blk: |v: RWLockWriteMode| -> U) -> U {
|
||||||
// Implementation slightly different from the slicker 'write's above.
|
// Implementation slightly different from the slicker 'write's above.
|
||||||
// The exit path is conditional on whether the caller downgrades.
|
// The exit path is conditional on whether the caller downgrades.
|
||||||
do task::unkillable {
|
|
||||||
(&self.order_lock).acquire();
|
(&self.order_lock).acquire();
|
||||||
(&self.access_lock).acquire();
|
(&self.access_lock).acquire();
|
||||||
(&self.order_lock).release();
|
(&self.order_lock).release();
|
||||||
do (|| {
|
do (|| {
|
||||||
do task::rekillable {
|
|
||||||
blk(RWLockWriteMode { lock: self, token: NonCopyable })
|
blk(RWLockWriteMode { lock: self, token: NonCopyable })
|
||||||
}
|
|
||||||
}).finally {
|
}).finally {
|
||||||
let writer_or_last_reader;
|
let writer_or_last_reader;
|
||||||
// Check if we're releasing from read mode or from write mode.
|
// Check if we're releasing from read mode or from write mode.
|
||||||
@ -634,7 +611,6 @@ impl RWLock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// To be called inside of the write_downgrade block.
|
/// To be called inside of the write_downgrade block.
|
||||||
pub fn downgrade<'a>(&self, token: RWLockWriteMode<'a>)
|
pub fn downgrade<'a>(&self, token: RWLockWriteMode<'a>)
|
||||||
@ -643,7 +619,6 @@ impl RWLock {
|
|||||||
fail!("Can't downgrade() with a different rwlock's write_mode!");
|
fail!("Can't downgrade() with a different rwlock's write_mode!");
|
||||||
}
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
do task::unkillable {
|
|
||||||
let state = &mut *self.state.get();
|
let state = &mut *self.state.get();
|
||||||
assert!(!state.read_mode);
|
assert!(!state.read_mode);
|
||||||
state.read_mode = true;
|
state.read_mode = true;
|
||||||
@ -661,7 +636,6 @@ impl RWLock {
|
|||||||
(&self.access_lock).release();
|
(&self.access_lock).release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
RWLockReadMode { lock: token.lock, token: NonCopyable }
|
RWLockReadMode { lock: token.lock, token: NonCopyable }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -867,7 +867,6 @@ pub fn run_test(force_ignore: bool,
|
|||||||
let testfn_cell = ::std::cell::Cell::new(testfn);
|
let testfn_cell = ::std::cell::Cell::new(testfn);
|
||||||
do task::spawn {
|
do task::spawn {
|
||||||
let mut task = task::task();
|
let mut task = task::task();
|
||||||
task.unlinked();
|
|
||||||
task.name(match desc.name {
|
task.name(match desc.name {
|
||||||
DynTestName(ref name) => SendStrOwned(name.clone()),
|
DynTestName(ref name) => SendStrOwned(name.clone()),
|
||||||
StaticTestName(name) => SendStrStatic(name),
|
StaticTestName(name) => SendStrStatic(name),
|
||||||
|
@ -954,10 +954,10 @@ mod tests {
|
|||||||
|
|
||||||
use std::f64;
|
use std::f64;
|
||||||
use std::result::{Err, Ok};
|
use std::result::{Err, Ok};
|
||||||
use std::libc;
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn set_time_zone() {
|
fn set_time_zone() {
|
||||||
|
use std::libc;
|
||||||
// Windows crt doesn't see any environment variable set by
|
// Windows crt doesn't see any environment variable set by
|
||||||
// `SetEnvironmentVariable`, which `os::setenv` internally uses.
|
// `SetEnvironmentVariable`, which `os::setenv` internally uses.
|
||||||
// It is why we use `putenv` here.
|
// It is why we use `putenv` here.
|
||||||
|
@ -338,7 +338,6 @@ pub fn monitor(f: proc(@diagnostic::Emitter)) {
|
|||||||
let ch_capture = ch.clone();
|
let ch_capture = ch.clone();
|
||||||
let mut task_builder = task::task();
|
let mut task_builder = task::task();
|
||||||
task_builder.name("rustc");
|
task_builder.name("rustc");
|
||||||
task_builder.supervised();
|
|
||||||
|
|
||||||
// XXX: Hacks on hacks. If the env is trying to override the stack size
|
// XXX: Hacks on hacks. If the env is trying to override the stack size
|
||||||
// then *don't* set it explicitly.
|
// then *don't* set it explicitly.
|
||||||
|
@ -708,10 +708,11 @@ impl Context {
|
|||||||
let prog_chan = prog_chan.clone();
|
let prog_chan = prog_chan.clone();
|
||||||
|
|
||||||
let mut task = task::task();
|
let mut task = task::task();
|
||||||
task.unlinked(); // we kill things manually
|
|
||||||
task.name(format!("worker{}", i));
|
task.name(format!("worker{}", i));
|
||||||
task.spawn_with(cache.clone(),
|
let cache = cache.clone();
|
||||||
|cache| worker(cache, &port, &chan, &prog_chan));
|
do task.spawn {
|
||||||
|
worker(cache, &port, &chan, &prog_chan);
|
||||||
|
}
|
||||||
|
|
||||||
fn worker(cache: RWArc<Cache>,
|
fn worker(cache: RWArc<Cache>,
|
||||||
port: &SharedPort<Work>,
|
port: &SharedPort<Work>,
|
||||||
|
@ -20,7 +20,6 @@ use std::io;
|
|||||||
use std::rt::local::Local;
|
use std::rt::local::Local;
|
||||||
use std::rt::rtio;
|
use std::rt::rtio;
|
||||||
use std::rt::sched::{Scheduler, SchedHandle};
|
use std::rt::sched::{Scheduler, SchedHandle};
|
||||||
use std::task;
|
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
use super::{Loop, UvError, uv_error_to_io_error, wait_until_woken_after};
|
use super::{Loop, UvError, uv_error_to_io_error, wait_until_woken_after};
|
||||||
@ -298,12 +297,11 @@ impl Drop for FsRequest {
|
|||||||
fn execute(f: &fn(*uvll::uv_fs_t, uvll::uv_fs_cb) -> c_int)
|
fn execute(f: &fn(*uvll::uv_fs_t, uvll::uv_fs_cb) -> c_int)
|
||||||
-> Result<FsRequest, UvError>
|
-> Result<FsRequest, UvError>
|
||||||
{
|
{
|
||||||
return do task::unkillable {
|
|
||||||
let mut req = FsRequest {
|
let mut req = FsRequest {
|
||||||
fired: false,
|
fired: false,
|
||||||
req: unsafe { uvll::malloc_req(uvll::UV_FS) }
|
req: unsafe { uvll::malloc_req(uvll::UV_FS) }
|
||||||
};
|
};
|
||||||
match f(req.req, fs_cb) {
|
return match f(req.req, fs_cb) {
|
||||||
0 => {
|
0 => {
|
||||||
req.fired = true;
|
req.fired = true;
|
||||||
let mut slot = None;
|
let mut slot = None;
|
||||||
@ -317,7 +315,6 @@ fn execute(f: &fn(*uvll::uv_fs_t, uvll::uv_fs_cb) -> c_int)
|
|||||||
}
|
}
|
||||||
n => Err(UvError(n))
|
n => Err(UvError(n))
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern fn fs_cb(req: *uvll::uv_fs_t) {
|
extern fn fs_cb(req: *uvll::uv_fs_t) {
|
||||||
|
@ -20,7 +20,6 @@ use std::rt::rtio;
|
|||||||
use std::rt::sched::{Scheduler, SchedHandle};
|
use std::rt::sched::{Scheduler, SchedHandle};
|
||||||
use std::rt::tube::Tube;
|
use std::rt::tube::Tube;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::task;
|
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
use stream::StreamWatcher;
|
use stream::StreamWatcher;
|
||||||
@ -176,7 +175,6 @@ impl TcpWatcher {
|
|||||||
{
|
{
|
||||||
struct Ctx { status: c_int, task: Option<BlockedTask> }
|
struct Ctx { status: c_int, task: Option<BlockedTask> }
|
||||||
|
|
||||||
return do task::unkillable {
|
|
||||||
let tcp = TcpWatcher::new(loop_);
|
let tcp = TcpWatcher::new(loop_);
|
||||||
let ret = do socket_addr_as_sockaddr(address) |addr| {
|
let ret = do socket_addr_as_sockaddr(address) |addr| {
|
||||||
let mut req = Request::new(uvll::UV_CONNECT);
|
let mut req = Request::new(uvll::UV_CONNECT);
|
||||||
@ -200,10 +198,9 @@ impl TcpWatcher {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match ret {
|
return match ret {
|
||||||
Ok(()) => Ok(tcp),
|
Ok(()) => Ok(tcp),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern fn connect_cb(req: *uvll::uv_connect_t, status: c_int) {
|
extern fn connect_cb(req: *uvll::uv_connect_t, status: c_int) {
|
||||||
@ -291,7 +288,6 @@ impl TcpListener {
|
|||||||
pub fn bind(loop_: &mut Loop, address: SocketAddr)
|
pub fn bind(loop_: &mut Loop, address: SocketAddr)
|
||||||
-> Result<~TcpListener, UvError>
|
-> Result<~TcpListener, UvError>
|
||||||
{
|
{
|
||||||
do task::unkillable {
|
|
||||||
let handle = unsafe { uvll::malloc_handle(uvll::UV_TCP) };
|
let handle = unsafe { uvll::malloc_handle(uvll::UV_TCP) };
|
||||||
assert_eq!(unsafe {
|
assert_eq!(unsafe {
|
||||||
uvll::uv_tcp_init(loop_.handle, handle)
|
uvll::uv_tcp_init(loop_.handle, handle)
|
||||||
@ -305,11 +301,10 @@ impl TcpListener {
|
|||||||
let res = socket_addr_as_sockaddr(address, |addr| unsafe {
|
let res = socket_addr_as_sockaddr(address, |addr| unsafe {
|
||||||
uvll::uv_tcp_bind(l.handle, addr)
|
uvll::uv_tcp_bind(l.handle, addr)
|
||||||
});
|
});
|
||||||
match res {
|
return match res {
|
||||||
0 => Ok(l.install()),
|
0 => Ok(l.install()),
|
||||||
n => Err(UvError(n))
|
n => Err(UvError(n))
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,7 +421,6 @@ impl UdpWatcher {
|
|||||||
pub fn bind(loop_: &Loop, address: SocketAddr)
|
pub fn bind(loop_: &Loop, address: SocketAddr)
|
||||||
-> Result<UdpWatcher, UvError>
|
-> Result<UdpWatcher, UvError>
|
||||||
{
|
{
|
||||||
do task::unkillable {
|
|
||||||
let udp = UdpWatcher {
|
let udp = UdpWatcher {
|
||||||
handle: unsafe { uvll::malloc_handle(uvll::UV_UDP) },
|
handle: unsafe { uvll::malloc_handle(uvll::UV_UDP) },
|
||||||
home: get_handle_to_current_scheduler!(),
|
home: get_handle_to_current_scheduler!(),
|
||||||
@ -437,11 +431,10 @@ impl UdpWatcher {
|
|||||||
let result = socket_addr_as_sockaddr(address, |addr| unsafe {
|
let result = socket_addr_as_sockaddr(address, |addr| unsafe {
|
||||||
uvll::uv_udp_bind(udp.handle, addr, 0u32)
|
uvll::uv_udp_bind(udp.handle, addr, 0u32)
|
||||||
});
|
});
|
||||||
match result {
|
return match result {
|
||||||
0 => Ok(udp),
|
0 => Ok(udp),
|
||||||
n => Err(UvError(n)),
|
n => Err(UvError(n)),
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1123,7 +1116,6 @@ mod test {
|
|||||||
assert!(maybe_socket.is_ok());
|
assert!(maybe_socket.is_ok());
|
||||||
|
|
||||||
// block self on sched1
|
// block self on sched1
|
||||||
do task::unkillable { // FIXME(#8674)
|
|
||||||
let scheduler: ~Scheduler = Local::take();
|
let scheduler: ~Scheduler = Local::take();
|
||||||
do scheduler.deschedule_running_task_and_then |_, task| {
|
do scheduler.deschedule_running_task_and_then |_, task| {
|
||||||
// unblock task
|
// unblock task
|
||||||
@ -1133,7 +1125,6 @@ mod test {
|
|||||||
};
|
};
|
||||||
// sched1 should now sleep since it has nothing else to do
|
// sched1 should now sleep since it has nothing else to do
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// sched2 will wake up and get the task as we do nothing else,
|
// sched2 will wake up and get the task as we do nothing else,
|
||||||
// the function ends and the socket goes out of scope sched2
|
// the function ends and the socket goes out of scope sched2
|
||||||
// will start to run the destructor the destructor will first
|
// will start to run the destructor the destructor will first
|
||||||
@ -1180,7 +1171,7 @@ mod test {
|
|||||||
let chan = Cell::new(chan);
|
let chan = Cell::new(chan);
|
||||||
let addr = next_test_ip4();
|
let addr = next_test_ip4();
|
||||||
|
|
||||||
do task::spawn_unlinked { // please no linked failure
|
do spawn {
|
||||||
let w = TcpListener::bind(local_loop(), addr).unwrap();
|
let w = TcpListener::bind(local_loop(), addr).unwrap();
|
||||||
let mut w = w.listen().unwrap();
|
let mut w = w.listen().unwrap();
|
||||||
chan.take().send(());
|
chan.take().send(());
|
||||||
|
@ -16,7 +16,6 @@ use std::rt::local::Local;
|
|||||||
use std::rt::rtio::{RtioPipe, RtioUnixListener, RtioUnixAcceptor};
|
use std::rt::rtio::{RtioPipe, RtioUnixListener, RtioUnixAcceptor};
|
||||||
use std::rt::sched::{Scheduler, SchedHandle};
|
use std::rt::sched::{Scheduler, SchedHandle};
|
||||||
use std::rt::tube::Tube;
|
use std::rt::tube::Tube;
|
||||||
use std::task;
|
|
||||||
|
|
||||||
use stream::StreamWatcher;
|
use stream::StreamWatcher;
|
||||||
use super::{Loop, UvError, UvHandle, Request, uv_error_to_io_error,
|
use super::{Loop, UvError, UvHandle, Request, uv_error_to_io_error,
|
||||||
@ -74,7 +73,6 @@ impl PipeWatcher {
|
|||||||
pub fn connect(loop_: &Loop, name: &CString) -> Result<PipeWatcher, UvError>
|
pub fn connect(loop_: &Loop, name: &CString) -> Result<PipeWatcher, UvError>
|
||||||
{
|
{
|
||||||
struct Ctx { task: Option<BlockedTask>, result: libc::c_int, }
|
struct Ctx { task: Option<BlockedTask>, result: libc::c_int, }
|
||||||
return do task::unkillable {
|
|
||||||
let mut cx = Ctx { task: None, result: 0 };
|
let mut cx = Ctx { task: None, result: 0 };
|
||||||
let mut req = Request::new(uvll::UV_CONNECT);
|
let mut req = Request::new(uvll::UV_CONNECT);
|
||||||
let pipe = PipeWatcher::new(loop_, false);
|
let pipe = PipeWatcher::new(loop_, false);
|
||||||
@ -89,11 +87,9 @@ impl PipeWatcher {
|
|||||||
req.set_data(&cx);
|
req.set_data(&cx);
|
||||||
req.defuse(); // uv callback now owns this request
|
req.defuse(); // uv callback now owns this request
|
||||||
}
|
}
|
||||||
match cx.result {
|
return match cx.result {
|
||||||
0 => Ok(pipe),
|
0 => Ok(pipe),
|
||||||
n => Err(UvError(n))
|
n => Err(UvError(n))
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern fn connect_cb(req: *uvll::uv_connect_t, status: libc::c_int) {;
|
extern fn connect_cb(req: *uvll::uv_connect_t, status: libc::c_int) {;
|
||||||
@ -153,7 +149,6 @@ extern fn pipe_close_cb(handle: *uvll::uv_handle_t) {
|
|||||||
|
|
||||||
impl PipeListener {
|
impl PipeListener {
|
||||||
pub fn bind(loop_: &Loop, name: &CString) -> Result<~PipeListener, UvError> {
|
pub fn bind(loop_: &Loop, name: &CString) -> Result<~PipeListener, UvError> {
|
||||||
do task::unkillable {
|
|
||||||
let pipe = PipeWatcher::new(loop_, false);
|
let pipe = PipeWatcher::new(loop_, false);
|
||||||
match unsafe {
|
match unsafe {
|
||||||
uvll::uv_pipe_bind(pipe.handle(), name.with_ref(|p| p))
|
uvll::uv_pipe_bind(pipe.handle(), name.with_ref(|p| p))
|
||||||
@ -172,7 +167,6 @@ impl PipeListener {
|
|||||||
n => Err(UvError(n))
|
n => Err(UvError(n))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RtioUnixListener for PipeListener {
|
impl RtioUnixListener for PipeListener {
|
||||||
@ -245,7 +239,6 @@ mod tests {
|
|||||||
use std::comm::oneshot;
|
use std::comm::oneshot;
|
||||||
use std::rt::rtio::{RtioUnixListener, RtioUnixAcceptor, RtioPipe};
|
use std::rt::rtio::{RtioUnixListener, RtioUnixAcceptor, RtioPipe};
|
||||||
use std::rt::test::next_test_unix;
|
use std::rt::test::next_test_unix;
|
||||||
use std::task;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use super::super::local_loop;
|
use super::super::local_loop;
|
||||||
@ -314,7 +307,7 @@ mod tests {
|
|||||||
let (port, chan) = oneshot();
|
let (port, chan) = oneshot();
|
||||||
let chan = Cell::new(chan);
|
let chan = Cell::new(chan);
|
||||||
|
|
||||||
do task::spawn_unlinked { // plz no linked failure
|
do spawn {
|
||||||
let p = PipeListener::bind(local_loop(), &path2.to_c_str()).unwrap();
|
let p = PipeListener::bind(local_loop(), &path2.to_c_str()).unwrap();
|
||||||
let mut p = p.listen().unwrap();
|
let mut p = p.listen().unwrap();
|
||||||
chan.take().send(());
|
chan.take().send(());
|
||||||
|
@ -44,11 +44,6 @@ pub trait HomingIO {
|
|||||||
fn go_to_IO_home(&mut self) -> uint {
|
fn go_to_IO_home(&mut self) -> uint {
|
||||||
use std::rt::sched::RunOnce;
|
use std::rt::sched::RunOnce;
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let task: *mut Task = Local::unsafe_borrow();
|
|
||||||
(*task).death.inhibit_kill((*task).unwinder.unwinding);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _f = ForbidUnwind::new("going home");
|
let _f = ForbidUnwind::new("going home");
|
||||||
|
|
||||||
let current_sched_id = do Local::borrow |sched: &mut Scheduler| {
|
let current_sched_id = do Local::borrow |sched: &mut Scheduler| {
|
||||||
@ -127,11 +122,6 @@ impl Drop for HomingMissile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
util::ignore(f);
|
util::ignore(f);
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let task: *mut Task = Local::unsafe_borrow();
|
|
||||||
(*task).death.allow_kill((*task).unwinder.unwinding);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -565,7 +565,6 @@ mod tests {
|
|||||||
($name:expr => $code:block) => (
|
($name:expr => $code:block) => (
|
||||||
{
|
{
|
||||||
let mut t = task::task();
|
let mut t = task::task();
|
||||||
t.supervised();
|
|
||||||
t.name($name);
|
t.name($name);
|
||||||
let res = do t.try $code;
|
let res = do t.try $code;
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
|
@ -1290,7 +1290,6 @@ mod tests {
|
|||||||
($name:expr => $code:block) => (
|
($name:expr => $code:block) => (
|
||||||
{
|
{
|
||||||
let mut t = task::task();
|
let mut t = task::task();
|
||||||
t.supervised();
|
|
||||||
t.name($name);
|
t.name($name);
|
||||||
let res = do t.try $code;
|
let res = do t.try $code;
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
|
@ -162,7 +162,7 @@ mod test {
|
|||||||
for _ in range(0, 20) {
|
for _ in range(0, 20) {
|
||||||
let (p, c) = comm::stream();
|
let (p, c) = comm::stream();
|
||||||
chans.push(c);
|
chans.push(c);
|
||||||
do task::spawn_with(p) |p| {
|
do task::spawn {
|
||||||
// wait until all the tasks are ready to go.
|
// wait until all the tasks are ready to go.
|
||||||
p.recv();
|
p.recv();
|
||||||
|
|
||||||
|
@ -155,176 +155,59 @@ use cell::Cell;
|
|||||||
use option::{Option, Some, None};
|
use option::{Option, Some, None};
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
use rt::task::Task;
|
use rt::task::Task;
|
||||||
use rt::task::{UnwindResult, Failure};
|
use rt::task::UnwindResult;
|
||||||
use task::spawn::Taskgroup;
|
use unstable::atomics::{AtomicUint, SeqCst};
|
||||||
use task::LinkedFailure;
|
use unstable::sync::UnsafeArc;
|
||||||
use to_bytes::IterBytes;
|
|
||||||
use unstable::atomics::{AtomicUint, Relaxed};
|
|
||||||
use unstable::sync::{UnsafeArc, UnsafeArcSelf, UnsafeArcT, LittleLock};
|
|
||||||
use util;
|
|
||||||
|
|
||||||
static KILLED_MSG: &'static str = "killed by linked failure";
|
|
||||||
|
|
||||||
// State values for the 'killed' and 'unkillable' atomic flags below.
|
|
||||||
static KILL_RUNNING: uint = 0;
|
|
||||||
static KILL_KILLED: uint = 1;
|
|
||||||
static KILL_UNKILLABLE: uint = 2;
|
|
||||||
|
|
||||||
struct KillFlag(AtomicUint);
|
|
||||||
type KillFlagHandle = UnsafeArc<KillFlag>;
|
|
||||||
|
|
||||||
/// A handle to a blocked task. Usually this means having the ~Task pointer by
|
/// A handle to a blocked task. Usually this means having the ~Task pointer by
|
||||||
/// ownership, but if the task is killable, a killer can steal it at any time.
|
/// ownership, but if the task is killable, a killer can steal it at any time.
|
||||||
pub enum BlockedTask {
|
pub enum BlockedTask {
|
||||||
Unkillable(~Task),
|
Owned(~Task),
|
||||||
Killable(KillFlagHandle),
|
Shared(UnsafeArc<AtomicUint>),
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME(#7544)(bblum): think about the cache efficiency of this
|
|
||||||
struct KillHandleInner {
|
|
||||||
// Is the task running, blocked, or killed? Possible values:
|
|
||||||
// * KILL_RUNNING - Not unkillable, no kill pending.
|
|
||||||
// * KILL_KILLED - Kill pending.
|
|
||||||
// * <ptr> - A transmuted blocked ~Task pointer.
|
|
||||||
// This flag is refcounted because it may also be referenced by a blocking
|
|
||||||
// concurrency primitive, used to wake the task normally, whose reference
|
|
||||||
// may outlive the handle's if the task is killed.
|
|
||||||
killed: KillFlagHandle,
|
|
||||||
// Has the task deferred kill signals? This flag guards the above one.
|
|
||||||
// Possible values:
|
|
||||||
// * KILL_RUNNING - Not unkillable, no kill pending.
|
|
||||||
// * KILL_KILLED - Kill pending.
|
|
||||||
// * KILL_UNKILLABLE - Kill signals deferred.
|
|
||||||
unkillable: AtomicUint,
|
|
||||||
|
|
||||||
// Shared state between task and children for exit code propagation. These
|
|
||||||
// are here so we can re-use the kill handle to implement watched children
|
|
||||||
// tasks. Using a separate Arc-like would introduce extra atomic adds/subs
|
|
||||||
// into common spawn paths, so this is just for speed.
|
|
||||||
|
|
||||||
// Locklessly accessed; protected by the enclosing refcount's barriers.
|
|
||||||
any_child_failed: bool,
|
|
||||||
// A lazy list, consuming which may unwrap() many child tombstones.
|
|
||||||
child_tombstones: Option<proc() -> bool>,
|
|
||||||
// Protects multiple children simultaneously creating tombstones.
|
|
||||||
graveyard_lock: LittleLock,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// State shared between tasks used for task killing during linked failure.
|
|
||||||
#[deriving(Clone)]
|
|
||||||
pub struct KillHandle(UnsafeArc<KillHandleInner>);
|
|
||||||
|
|
||||||
/// Per-task state related to task death, killing, failure, etc.
|
/// Per-task state related to task death, killing, failure, etc.
|
||||||
pub struct Death {
|
pub struct Death {
|
||||||
// Shared among this task, its watched children, and any linked tasks who
|
|
||||||
// might kill it. This is optional so we can take it by-value at exit time.
|
|
||||||
kill_handle: Option<KillHandle>,
|
|
||||||
// Handle to a watching parent, if we have one, for exit code propagation.
|
|
||||||
priv watching_parent: Option<KillHandle>,
|
|
||||||
// Action to be done with the exit code. If set, also makes the task wait
|
// Action to be done with the exit code. If set, also makes the task wait
|
||||||
// until all its watched children exit before collecting the status.
|
// until all its watched children exit before collecting the status.
|
||||||
on_exit: Option<proc(UnwindResult)>,
|
on_exit: Option<proc(UnwindResult)>,
|
||||||
// nesting level counter for task::unkillable calls (0 == killable).
|
|
||||||
priv unkillable: int,
|
|
||||||
// nesting level counter for unstable::atomically calls (0 == can deschedule).
|
// nesting level counter for unstable::atomically calls (0 == can deschedule).
|
||||||
priv wont_sleep: int,
|
priv wont_sleep: int,
|
||||||
// A "spare" handle to the kill flag inside the kill handle. Used during
|
|
||||||
// blocking/waking as an optimization to avoid two xadds on the refcount.
|
|
||||||
priv spare_kill_flag: Option<KillFlagHandle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for KillFlag {
|
|
||||||
// Letting a KillFlag with a task inside get dropped would leak the task.
|
|
||||||
// We could free it here, but the task should get awoken by hand somehow.
|
|
||||||
fn drop(&mut self) {
|
|
||||||
match self.load(Relaxed) {
|
|
||||||
KILL_RUNNING | KILL_KILLED => { },
|
|
||||||
_ => rtabort!("can't drop kill flag with a blocked task inside!"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whenever a task blocks, it swaps out its spare kill flag to use as the
|
|
||||||
// blocked task handle. So unblocking a task must restore that spare.
|
|
||||||
unsafe fn revive_task_ptr(task_ptr: uint, spare_flag: Option<KillFlagHandle>) -> ~Task {
|
|
||||||
let mut task: ~Task = cast::transmute(task_ptr);
|
|
||||||
if task.death.spare_kill_flag.is_none() {
|
|
||||||
task.death.spare_kill_flag = spare_flag;
|
|
||||||
} else {
|
|
||||||
// A task's spare kill flag is not used for blocking in one case:
|
|
||||||
// when an unkillable task blocks on select. In this case, a separate
|
|
||||||
// one was created, which we now discard.
|
|
||||||
rtassert!(task.death.unkillable > 0);
|
|
||||||
}
|
|
||||||
task
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockedTask {
|
impl BlockedTask {
|
||||||
/// Returns Some if the task was successfully woken; None if already killed.
|
/// Returns Some if the task was successfully woken; None if already killed.
|
||||||
pub fn wake(self) -> Option<~Task> {
|
pub fn wake(self) -> Option<~Task> {
|
||||||
match self {
|
match self {
|
||||||
Unkillable(task) => Some(task),
|
Owned(task) => Some(task),
|
||||||
Killable(flag_arc) => {
|
Shared(arc) => unsafe {
|
||||||
let flag = unsafe { &mut **flag_arc.get() };
|
match (*arc.get()).swap(0, SeqCst) {
|
||||||
match flag.swap(KILL_RUNNING, Relaxed) {
|
0 => None,
|
||||||
KILL_RUNNING => None, // woken from select(), perhaps
|
n => cast::transmute(n),
|
||||||
KILL_KILLED => None, // a killer stole it already
|
|
||||||
task_ptr =>
|
|
||||||
Some(unsafe { revive_task_ptr(task_ptr, Some(flag_arc)) })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a blocked task, unless the task was already killed.
|
/// Create a blocked task, unless the task was already killed.
|
||||||
pub fn try_block(mut task: ~Task) -> Either<~Task, BlockedTask> {
|
pub fn block(task: ~Task) -> BlockedTask {
|
||||||
// NB: As an optimization, we could give a free pass to being unkillable
|
Owned(task)
|
||||||
// to tasks whose taskgroups haven't been initialized yet, but that
|
|
||||||
// introduces complications with select() and with the test cases below,
|
|
||||||
// and it's not clear the uncommon performance boost is worth it.
|
|
||||||
if task.death.unkillable > 0 {
|
|
||||||
Right(Unkillable(task))
|
|
||||||
} else {
|
|
||||||
rtassert!(task.death.kill_handle.is_some());
|
|
||||||
unsafe {
|
|
||||||
// The inverse of 'revive', above, occurs here.
|
|
||||||
// The spare kill flag will usually be Some, unless the task was
|
|
||||||
// already killed, in which case the killer will have deferred
|
|
||||||
// creating a new one until whenever it blocks during unwinding.
|
|
||||||
let flag_arc = match task.death.spare_kill_flag.take() {
|
|
||||||
Some(spare_flag) => spare_flag,
|
|
||||||
None => {
|
|
||||||
// A task that kills us won't have a spare kill flag to
|
|
||||||
// give back to us, so we restore it ourselves here. This
|
|
||||||
// situation should only arise when we're already failing.
|
|
||||||
rtassert!(task.unwinder.unwinding);
|
|
||||||
(*task.death.kill_handle.get_ref().get()).killed.clone()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let flag = &mut **flag_arc.get();
|
|
||||||
let task_ptr = cast::transmute(task);
|
|
||||||
// Expect flag to contain RUNNING. If KILLED, it should stay KILLED.
|
|
||||||
match flag.compare_and_swap(KILL_RUNNING, task_ptr, Relaxed) {
|
|
||||||
KILL_RUNNING => Right(Killable(flag_arc)),
|
|
||||||
KILL_KILLED => Left(revive_task_ptr(task_ptr, Some(flag_arc))),
|
|
||||||
x => rtabort!("can't block task! kill flag = {}", x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts one blocked task handle to a list of many handles to the same.
|
/// Converts one blocked task handle to a list of many handles to the same.
|
||||||
pub fn make_selectable(self, num_handles: uint) -> ~[BlockedTask] {
|
pub fn make_selectable(self, num_handles: uint) -> ~[BlockedTask] {
|
||||||
let handles = match self {
|
let handles = match self {
|
||||||
Unkillable(task) => {
|
Owned(task) => {
|
||||||
let flag = unsafe { KillFlag(AtomicUint::new(cast::transmute(task))) };
|
let flag = unsafe {
|
||||||
|
AtomicUint::new(cast::transmute(task))
|
||||||
|
};
|
||||||
UnsafeArc::newN(flag, num_handles)
|
UnsafeArc::newN(flag, num_handles)
|
||||||
}
|
}
|
||||||
Killable(flag_arc) => flag_arc.cloneN(num_handles),
|
Shared(arc) => arc.cloneN(num_handles),
|
||||||
};
|
};
|
||||||
// Even if the task was unkillable before, we use 'Killable' because
|
// Even if the task was unkillable before, we use 'Killable' because
|
||||||
// multiple pipes will have handles. It does not really mean killable.
|
// multiple pipes will have handles. It does not really mean killable.
|
||||||
handles.move_iter().map(|x| Killable(x)).collect()
|
handles.move_iter().map(|x| Shared(x)).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// This assertion has two flavours because the wake involves an atomic op.
|
// This assertion has two flavours because the wake involves an atomic op.
|
||||||
@ -337,16 +220,14 @@ impl BlockedTask {
|
|||||||
/// Convert to an unsafe uint value. Useful for storing in a pipe's state flag.
|
/// Convert to an unsafe uint value. Useful for storing in a pipe's state flag.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub unsafe fn cast_to_uint(self) -> uint {
|
pub unsafe fn cast_to_uint(self) -> uint {
|
||||||
// Use the low bit to distinguish the enum variants, to save a second
|
|
||||||
// allocation in the indestructible case.
|
|
||||||
match self {
|
match self {
|
||||||
Unkillable(task) => {
|
Owned(task) => {
|
||||||
let blocked_task_ptr: uint = cast::transmute(task);
|
let blocked_task_ptr: uint = cast::transmute(task);
|
||||||
rtassert!(blocked_task_ptr & 0x1 == 0);
|
rtassert!(blocked_task_ptr & 0x1 == 0);
|
||||||
blocked_task_ptr
|
blocked_task_ptr
|
||||||
},
|
}
|
||||||
Killable(flag_arc) => {
|
Shared(arc) => {
|
||||||
let blocked_task_ptr: uint = cast::transmute(~flag_arc);
|
let blocked_task_ptr: uint = cast::transmute(~arc);
|
||||||
rtassert!(blocked_task_ptr & 0x1 == 0);
|
rtassert!(blocked_task_ptr & 0x1 == 0);
|
||||||
blocked_task_ptr | 0x1
|
blocked_task_ptr | 0x1
|
||||||
}
|
}
|
||||||
@ -357,318 +238,29 @@ impl BlockedTask {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub unsafe fn cast_from_uint(blocked_task_ptr: uint) -> BlockedTask {
|
pub unsafe fn cast_from_uint(blocked_task_ptr: uint) -> BlockedTask {
|
||||||
if blocked_task_ptr & 0x1 == 0 {
|
if blocked_task_ptr & 0x1 == 0 {
|
||||||
Unkillable(cast::transmute(blocked_task_ptr))
|
Owned(cast::transmute(blocked_task_ptr))
|
||||||
} else {
|
} else {
|
||||||
let ptr: ~KillFlagHandle = cast::transmute(blocked_task_ptr & !0x1);
|
let ptr: ~UnsafeArc<AtomicUint> = cast::transmute(blocked_task_ptr & !1);
|
||||||
match ptr {
|
Shared(*ptr)
|
||||||
~flag_arc => Killable(flag_arc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// So that KillHandle can be hashed in the taskgroup bookkeeping code.
|
|
||||||
impl IterBytes for KillHandle {
|
|
||||||
fn iter_bytes(&self, lsb0: bool, f: |buf: &[u8]| -> bool) -> bool {
|
|
||||||
self.data.iter_bytes(lsb0, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Eq for KillHandle {
|
|
||||||
#[inline] fn eq(&self, other: &KillHandle) -> bool { self.data.eq(&other.data) }
|
|
||||||
#[inline] fn ne(&self, other: &KillHandle) -> bool { self.data.ne(&other.data) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KillHandle {
|
|
||||||
pub fn new() -> (KillHandle, KillFlagHandle) {
|
|
||||||
let (flag, flag_clone) =
|
|
||||||
UnsafeArc::new2(KillFlag(AtomicUint::new(KILL_RUNNING)));
|
|
||||||
let handle = KillHandle(UnsafeArc::new(KillHandleInner {
|
|
||||||
// Linked failure fields
|
|
||||||
killed: flag,
|
|
||||||
unkillable: AtomicUint::new(KILL_RUNNING),
|
|
||||||
// Exit code propagation fields
|
|
||||||
any_child_failed: false,
|
|
||||||
child_tombstones: None,
|
|
||||||
graveyard_lock: LittleLock::new(),
|
|
||||||
}));
|
|
||||||
(handle, flag_clone)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Will begin unwinding if a kill signal was received, unless already_failing.
|
|
||||||
// This can't be used recursively, because a task which sees a KILLED
|
|
||||||
// signal must fail immediately, which an already-unkillable task can't do.
|
|
||||||
#[inline]
|
|
||||||
pub fn inhibit_kill(&mut self, already_failing: bool) {
|
|
||||||
let inner = unsafe { &mut *self.get() };
|
|
||||||
// Expect flag to contain RUNNING. If KILLED, it should stay KILLED.
|
|
||||||
// FIXME(#7544)(bblum): is it really necessary to prohibit double kill?
|
|
||||||
match inner.unkillable.compare_and_swap(KILL_RUNNING, KILL_UNKILLABLE, Relaxed) {
|
|
||||||
KILL_RUNNING => { }, // normal case
|
|
||||||
KILL_KILLED => if !already_failing { fail!("{}", KILLED_MSG) },
|
|
||||||
_ => rtabort!("inhibit_kill: task already unkillable"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Will begin unwinding if a kill signal was received, unless already_failing.
|
|
||||||
#[inline]
|
|
||||||
pub fn allow_kill(&mut self, already_failing: bool) {
|
|
||||||
let inner = unsafe { &mut *self.get() };
|
|
||||||
// Expect flag to contain UNKILLABLE. If KILLED, it should stay KILLED.
|
|
||||||
// FIXME(#7544)(bblum): is it really necessary to prohibit double kill?
|
|
||||||
match inner.unkillable.compare_and_swap(KILL_UNKILLABLE, KILL_RUNNING, Relaxed) {
|
|
||||||
KILL_UNKILLABLE => { }, // normal case
|
|
||||||
KILL_KILLED => if !already_failing { fail!("{}", KILLED_MSG) },
|
|
||||||
_ => rtabort!("allow_kill: task already killable"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a kill signal to the handle's owning task. Returns the task itself
|
|
||||||
// if it was blocked and needs punted awake. To be called by other tasks.
|
|
||||||
pub fn kill(&mut self) -> Option<~Task> {
|
|
||||||
let inner = unsafe { &mut *self.get() };
|
|
||||||
if inner.unkillable.swap(KILL_KILLED, Relaxed) == KILL_RUNNING {
|
|
||||||
// Got in. Allowed to try to punt the task awake.
|
|
||||||
let flag = unsafe { &mut *inner.killed.get() };
|
|
||||||
match flag.swap(KILL_KILLED, Relaxed) {
|
|
||||||
// Task either not blocked or already taken care of.
|
|
||||||
KILL_RUNNING | KILL_KILLED => None,
|
|
||||||
// Got ownership of the blocked task.
|
|
||||||
// While the usual 'wake' path can just pass back the flag
|
|
||||||
// handle, we (the slower kill path) haven't an extra one lying
|
|
||||||
// around. The task will wake up without a spare.
|
|
||||||
task_ptr => Some(unsafe { revive_task_ptr(task_ptr, None) }),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise it was either unkillable or already killed. Somebody
|
|
||||||
// else was here first who will deal with the kill signal.
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn killed(&self) -> bool {
|
|
||||||
// Called every context switch, so shouldn't report true if the task
|
|
||||||
// is unkillable with a kill signal pending.
|
|
||||||
let inner = unsafe { &*self.get() };
|
|
||||||
let flag = unsafe { &*inner.killed.get() };
|
|
||||||
// A barrier-related concern here is that a task that gets killed
|
|
||||||
// awake needs to see the killer's write of KILLED to this flag. This
|
|
||||||
// is analogous to receiving a pipe payload; the appropriate barrier
|
|
||||||
// should happen when enqueueing the task.
|
|
||||||
flag.load(Relaxed) == KILL_KILLED
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn notify_immediate_failure(&mut self) {
|
|
||||||
// A benign data race may happen here if there are failing sibling
|
|
||||||
// tasks that were also spawned-watched. The refcount's write barriers
|
|
||||||
// in UnsafeArc ensure that this write will be seen by the
|
|
||||||
// unwrapper/destructor, whichever task may unwrap it.
|
|
||||||
unsafe { (*self.get()).any_child_failed = true; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// For use when a task does not need to collect its children's exit
|
|
||||||
// statuses, but the task has a parent which might want them.
|
|
||||||
pub fn reparent_children_to(self, parent: &mut KillHandle) {
|
|
||||||
// Optimistic path: If another child of the parent's already failed,
|
|
||||||
// we don't need to worry about any of this.
|
|
||||||
if unsafe { (*parent.get()).any_child_failed } {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to see if all our children are gone already.
|
|
||||||
match self.try_unwrap() {
|
|
||||||
// Couldn't unwrap; children still alive. Reparent entire handle as
|
|
||||||
// our own tombstone, to be unwrapped later.
|
|
||||||
UnsafeArcSelf(this) => {
|
|
||||||
let this = Cell::new(this); // :(
|
|
||||||
do add_lazy_tombstone(parent) |other_tombstones| {
|
|
||||||
let this = Cell::new(this.take()); // :(
|
|
||||||
let others = Cell::new(other_tombstones); // :(
|
|
||||||
|| {
|
|
||||||
// Prefer to check tombstones that were there first,
|
|
||||||
// being "more fair" at the expense of tail-recursion.
|
|
||||||
others.take().map_default(true, |f| f()) && {
|
|
||||||
let mut inner = this.take().unwrap();
|
|
||||||
(!inner.any_child_failed) &&
|
|
||||||
inner.child_tombstones.take().map_default(true, |f| f())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whether or not all children exited, one or more already failed.
|
|
||||||
UnsafeArcT(KillHandleInner { any_child_failed: true, _ }) => {
|
|
||||||
parent.notify_immediate_failure();
|
|
||||||
}
|
|
||||||
|
|
||||||
// All children exited, but some left behind tombstones that we
|
|
||||||
// don't want to wait on now. Give them to our parent.
|
|
||||||
UnsafeArcT(KillHandleInner { any_child_failed: false,
|
|
||||||
child_tombstones: Some(f), _ }) => {
|
|
||||||
let f = Cell::new(f); // :(
|
|
||||||
do add_lazy_tombstone(parent) |other_tombstones| {
|
|
||||||
let f = Cell::new(f.take()); // :(
|
|
||||||
let others = Cell::new(other_tombstones); // :(
|
|
||||||
|| {
|
|
||||||
// Prefer fairness to tail-recursion, as in above case.
|
|
||||||
others.take().map_default(true, |f| f()) &&
|
|
||||||
f.take()()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// All children exited, none failed. Nothing to do!
|
|
||||||
UnsafeArcT(KillHandleInner { any_child_failed: false,
|
|
||||||
child_tombstones: None, _ }) => { }
|
|
||||||
}
|
|
||||||
|
|
||||||
// NB: Takes a pthread mutex -- 'blk' not allowed to reschedule.
|
|
||||||
#[inline]
|
|
||||||
fn add_lazy_tombstone(parent: &mut KillHandle,
|
|
||||||
blk: |Option<proc() -> bool>| -> proc() -> bool)
|
|
||||||
{
|
|
||||||
let inner: &mut KillHandleInner = unsafe { &mut *parent.get() };
|
|
||||||
unsafe {
|
|
||||||
do inner.graveyard_lock.lock {
|
|
||||||
// Update the current "head node" of the lazy list.
|
|
||||||
inner.child_tombstones =
|
|
||||||
Some(blk(util::replace(&mut inner.child_tombstones, None)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Death {
|
impl Death {
|
||||||
pub fn new() -> Death {
|
pub fn new() -> Death {
|
||||||
let (handle, spare) = KillHandle::new();
|
|
||||||
Death {
|
Death {
|
||||||
kill_handle: Some(handle),
|
|
||||||
watching_parent: None,
|
|
||||||
on_exit: None,
|
on_exit: None,
|
||||||
unkillable: 0,
|
|
||||||
wont_sleep: 0,
|
wont_sleep: 0,
|
||||||
spare_kill_flag: Some(spare),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_child(&self) -> Death {
|
|
||||||
// FIXME(#7327)
|
|
||||||
let (handle, spare) = KillHandle::new();
|
|
||||||
Death {
|
|
||||||
kill_handle: Some(handle),
|
|
||||||
watching_parent: self.kill_handle.clone(),
|
|
||||||
on_exit: None,
|
|
||||||
unkillable: 0,
|
|
||||||
wont_sleep: 0,
|
|
||||||
spare_kill_flag: Some(spare),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect failure exit codes from children and propagate them to a parent.
|
/// Collect failure exit codes from children and propagate them to a parent.
|
||||||
pub fn collect_failure(&mut self, result: UnwindResult, group: Option<Taskgroup>) {
|
pub fn collect_failure(&mut self, result: UnwindResult) {
|
||||||
// This may run after the task has already failed, so even though the
|
let result = Cell::new(result);
|
||||||
// task appears to need to be killed, the scheduler should not fail us
|
|
||||||
// when we block to unwrap.
|
|
||||||
// (XXX: Another less-elegant reason for doing this is so that the use
|
|
||||||
// of the LittleLock in reparent_children_to doesn't need to access the
|
|
||||||
// unkillable flag in the kill_handle, since we'll have removed it.)
|
|
||||||
rtassert!(self.unkillable == 0);
|
|
||||||
self.unkillable = 1;
|
|
||||||
|
|
||||||
// NB. See corresponding comment at the callsite in task.rs.
|
|
||||||
// FIXME(#8192): Doesn't work with "let _ = ..."
|
|
||||||
{ use util; util::ignore(group); }
|
|
||||||
|
|
||||||
let mut success = result.is_success();
|
|
||||||
let mut result = Cell::new(result);
|
|
||||||
|
|
||||||
// Step 1. Decide if we need to collect child failures synchronously.
|
|
||||||
do self.on_exit.take().map |on_exit| {
|
do self.on_exit.take().map |on_exit| {
|
||||||
if success {
|
|
||||||
// We succeeded, but our children might not. Need to wait for them.
|
|
||||||
let mut inner = self.kill_handle.take_unwrap().unwrap();
|
|
||||||
|
|
||||||
if inner.any_child_failed {
|
|
||||||
success = false;
|
|
||||||
} else {
|
|
||||||
// Lockless access to tombstones protected by unwrap barrier.
|
|
||||||
success = inner.child_tombstones.take().map_default(true, |f| f());
|
|
||||||
}
|
|
||||||
|
|
||||||
if !success {
|
|
||||||
result = Cell::new(Failure(~LinkedFailure as ~Any));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
on_exit(result.take());
|
on_exit(result.take());
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 2. Possibly alert possibly-watching parent to failure status.
|
|
||||||
// Note that as soon as parent_handle goes out of scope, the parent
|
|
||||||
// can successfully unwrap its handle and collect our reported status.
|
|
||||||
do self.watching_parent.take().map |mut parent_handle| {
|
|
||||||
if success {
|
|
||||||
// Our handle might be None if we had an exit callback, and
|
|
||||||
// already unwrapped it. But 'success' being true means no
|
|
||||||
// child failed, so there's nothing to do (see below case).
|
|
||||||
do self.kill_handle.take().map |own_handle| {
|
|
||||||
own_handle.reparent_children_to(&mut parent_handle);
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// Can inform watching parent immediately that we failed.
|
|
||||||
// (Note the importance of non-failing tasks NOT writing
|
|
||||||
// 'false', which could obscure another task's failure.)
|
|
||||||
parent_handle.notify_immediate_failure();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Can't use allow_kill directly; that would require the kill handle.
|
|
||||||
rtassert!(self.unkillable == 1);
|
|
||||||
self.unkillable = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fails if a kill signal was received.
|
|
||||||
#[inline]
|
|
||||||
pub fn check_killed(&self, already_failing: bool) {
|
|
||||||
match self.kill_handle {
|
|
||||||
Some(ref kill_handle) =>
|
|
||||||
// The task may be both unkillable and killed if it does some
|
|
||||||
// synchronization during unwinding or cleanup (for example,
|
|
||||||
// sending on a notify port). In that case failing won't help.
|
|
||||||
if self.unkillable == 0 && (!already_failing) && kill_handle.killed() {
|
|
||||||
fail!("{}", KILLED_MSG);
|
|
||||||
},
|
|
||||||
// This may happen during task death (see comments in collect_failure).
|
|
||||||
None => rtassert!(self.unkillable > 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enter a possibly-nested unkillable section of code.
|
|
||||||
/// All calls must be paired with a subsequent call to allow_kill.
|
|
||||||
#[inline]
|
|
||||||
pub fn inhibit_kill(&mut self, already_failing: bool) {
|
|
||||||
self.unkillable += 1;
|
|
||||||
// May fail, hence must happen *after* incrementing the counter
|
|
||||||
if self.unkillable == 1 {
|
|
||||||
rtassert!(self.kill_handle.is_some());
|
|
||||||
self.kill_handle.get_mut_ref().inhibit_kill(already_failing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Exit a possibly-nested unkillable section of code.
|
|
||||||
/// All calls must be paired with a preceding call to inhibit_kill.
|
|
||||||
#[inline]
|
|
||||||
pub fn allow_kill(&mut self, already_failing: bool) {
|
|
||||||
if self.unkillable == 0 {
|
|
||||||
// we need to decrement the counter before failing.
|
|
||||||
self.unkillable -= 1;
|
|
||||||
fail!("Cannot enter a rekillable() block without a surrounding unkillable()");
|
|
||||||
}
|
|
||||||
self.unkillable -= 1;
|
|
||||||
if self.unkillable == 0 {
|
|
||||||
rtassert!(self.kill_handle.is_some());
|
|
||||||
self.kill_handle.get_mut_ref().allow_kill(already_failing);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enter a possibly-nested "atomic" section of code. Just for assertions.
|
/// Enter a possibly-nested "atomic" section of code. Just for assertions.
|
||||||
@ -699,296 +291,21 @@ impl Death {
|
|||||||
impl Drop for Death {
|
impl Drop for Death {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// Mustn't be in an atomic or unkillable section at task death.
|
// Mustn't be in an atomic or unkillable section at task death.
|
||||||
rtassert!(self.unkillable == 0);
|
|
||||||
rtassert!(self.wont_sleep == 0);
|
rtassert!(self.wont_sleep == 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
#[allow(unused_mut)];
|
|
||||||
use cell::Cell;
|
|
||||||
use rt::test::*;
|
use rt::test::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
use util;
|
|
||||||
|
|
||||||
// Test cases don't care about the spare killed flag.
|
|
||||||
fn make_kill_handle() -> KillHandle { let (h,_) = KillHandle::new(); h }
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn no_tombstone_success() {
|
|
||||||
do run_in_newsched_task {
|
|
||||||
// Tests case 4 of the 4-way match in reparent_children.
|
|
||||||
let mut parent = make_kill_handle();
|
|
||||||
let mut child = make_kill_handle();
|
|
||||||
|
|
||||||
// Without another handle to child, the try unwrap should succeed.
|
|
||||||
child.reparent_children_to(&mut parent);
|
|
||||||
let mut parent_inner = parent.unwrap();
|
|
||||||
assert!(parent_inner.child_tombstones.is_none());
|
|
||||||
assert!(parent_inner.any_child_failed == false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn no_tombstone_failure() {
|
|
||||||
do run_in_newsched_task {
|
|
||||||
// Tests case 2 of the 4-way match in reparent_children.
|
|
||||||
let mut parent = make_kill_handle();
|
|
||||||
let mut child = make_kill_handle();
|
|
||||||
|
|
||||||
child.notify_immediate_failure();
|
|
||||||
// Without another handle to child, the try unwrap should succeed.
|
|
||||||
child.reparent_children_to(&mut parent);
|
|
||||||
let mut parent_inner = parent.unwrap();
|
|
||||||
assert!(parent_inner.child_tombstones.is_none());
|
|
||||||
// Immediate failure should have been propagated.
|
|
||||||
assert!(parent_inner.any_child_failed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn no_tombstone_because_sibling_already_failed() {
|
|
||||||
do run_in_newsched_task {
|
|
||||||
// Tests "case 0, the optimistic path in reparent_children.
|
|
||||||
let mut parent = make_kill_handle();
|
|
||||||
let mut child1 = make_kill_handle();
|
|
||||||
let mut child2 = make_kill_handle();
|
|
||||||
let mut link = child2.clone();
|
|
||||||
|
|
||||||
// Should set parent's child_failed flag
|
|
||||||
child1.notify_immediate_failure();
|
|
||||||
child1.reparent_children_to(&mut parent);
|
|
||||||
// Should bypass trying to unwrap child2 entirely.
|
|
||||||
// Otherwise, due to 'link', it would try to tombstone.
|
|
||||||
child2.reparent_children_to(&mut parent);
|
|
||||||
// Should successfully unwrap even though 'link' is still alive.
|
|
||||||
let mut parent_inner = parent.unwrap();
|
|
||||||
assert!(parent_inner.child_tombstones.is_none());
|
|
||||||
// Immediate failure should have been propagated by first child.
|
|
||||||
assert!(parent_inner.any_child_failed);
|
|
||||||
util::ignore(link);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn one_tombstone_success() {
|
|
||||||
do run_in_newsched_task {
|
|
||||||
let mut parent = make_kill_handle();
|
|
||||||
let mut child = make_kill_handle();
|
|
||||||
let mut link = child.clone();
|
|
||||||
|
|
||||||
// Creates 1 tombstone. Existence of 'link' makes try-unwrap fail.
|
|
||||||
child.reparent_children_to(&mut parent);
|
|
||||||
// Let parent collect tombstones.
|
|
||||||
util::ignore(link);
|
|
||||||
// Must have created a tombstone
|
|
||||||
let mut parent_inner = parent.unwrap();
|
|
||||||
assert!(parent_inner.child_tombstones.take_unwrap()());
|
|
||||||
assert!(parent_inner.any_child_failed == false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn one_tombstone_failure() {
|
|
||||||
do run_in_newsched_task {
|
|
||||||
let mut parent = make_kill_handle();
|
|
||||||
let mut child = make_kill_handle();
|
|
||||||
let mut link = child.clone();
|
|
||||||
|
|
||||||
// Creates 1 tombstone. Existence of 'link' makes try-unwrap fail.
|
|
||||||
child.reparent_children_to(&mut parent);
|
|
||||||
// Must happen after tombstone to not be immediately propagated.
|
|
||||||
link.notify_immediate_failure();
|
|
||||||
// Let parent collect tombstones.
|
|
||||||
util::ignore(link);
|
|
||||||
// Must have created a tombstone
|
|
||||||
let mut parent_inner = parent.unwrap();
|
|
||||||
// Failure must be seen in the tombstone.
|
|
||||||
assert!(parent_inner.child_tombstones.take_unwrap()() == false);
|
|
||||||
assert!(parent_inner.any_child_failed == false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn two_tombstones_success() {
|
|
||||||
do run_in_newsched_task {
|
|
||||||
let mut parent = make_kill_handle();
|
|
||||||
let mut middle = make_kill_handle();
|
|
||||||
let mut child = make_kill_handle();
|
|
||||||
let mut link = child.clone();
|
|
||||||
|
|
||||||
child.reparent_children_to(&mut middle); // case 1 tombstone
|
|
||||||
// 'middle' should try-unwrap okay, but still have to reparent.
|
|
||||||
middle.reparent_children_to(&mut parent); // case 3 tombston
|
|
||||||
// Let parent collect tombstones.
|
|
||||||
util::ignore(link);
|
|
||||||
// Must have created a tombstone
|
|
||||||
let mut parent_inner = parent.unwrap();
|
|
||||||
assert!(parent_inner.child_tombstones.take_unwrap()());
|
|
||||||
assert!(parent_inner.any_child_failed == false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn two_tombstones_failure() {
|
|
||||||
do run_in_newsched_task {
|
|
||||||
let mut parent = make_kill_handle();
|
|
||||||
let mut middle = make_kill_handle();
|
|
||||||
let mut child = make_kill_handle();
|
|
||||||
let mut link = child.clone();
|
|
||||||
|
|
||||||
child.reparent_children_to(&mut middle); // case 1 tombstone
|
|
||||||
// Must happen after tombstone to not be immediately propagated.
|
|
||||||
link.notify_immediate_failure();
|
|
||||||
// 'middle' should try-unwrap okay, but still have to reparent.
|
|
||||||
middle.reparent_children_to(&mut parent); // case 3 tombstone
|
|
||||||
// Let parent collect tombstones.
|
|
||||||
util::ignore(link);
|
|
||||||
// Must have created a tombstone
|
|
||||||
let mut parent_inner = parent.unwrap();
|
|
||||||
// Failure must be seen in the tombstone.
|
|
||||||
assert!(parent_inner.child_tombstones.take_unwrap()() == false);
|
|
||||||
assert!(parent_inner.any_child_failed == false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Task killing tests
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn kill_basic() {
|
|
||||||
do run_in_newsched_task {
|
|
||||||
let mut handle = make_kill_handle();
|
|
||||||
assert!(!handle.killed());
|
|
||||||
assert!(handle.kill().is_none());
|
|
||||||
assert!(handle.killed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn double_kill() {
|
|
||||||
do run_in_newsched_task {
|
|
||||||
let mut handle = make_kill_handle();
|
|
||||||
assert!(!handle.killed());
|
|
||||||
assert!(handle.kill().is_none());
|
|
||||||
assert!(handle.killed());
|
|
||||||
assert!(handle.kill().is_none());
|
|
||||||
assert!(handle.killed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unkillable_after_kill() {
|
|
||||||
do run_in_newsched_task {
|
|
||||||
let mut handle = make_kill_handle();
|
|
||||||
assert!(handle.kill().is_none());
|
|
||||||
assert!(handle.killed());
|
|
||||||
let handle_cell = Cell::new(handle);
|
|
||||||
let result = do spawntask_try {
|
|
||||||
handle_cell.take().inhibit_kill(false);
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unkillable_during_kill() {
|
|
||||||
do run_in_newsched_task {
|
|
||||||
let mut handle = make_kill_handle();
|
|
||||||
handle.inhibit_kill(false);
|
|
||||||
assert!(handle.kill().is_none());
|
|
||||||
assert!(!handle.killed());
|
|
||||||
let handle_cell = Cell::new(handle);
|
|
||||||
let result = do spawntask_try {
|
|
||||||
handle_cell.take().allow_kill(false);
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn unkillable_before_kill() {
|
|
||||||
do run_in_newsched_task {
|
|
||||||
let mut handle = make_kill_handle();
|
|
||||||
handle.inhibit_kill(false);
|
|
||||||
handle.allow_kill(false);
|
|
||||||
assert!(handle.kill().is_none());
|
|
||||||
assert!(handle.killed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Task blocking tests
|
// Task blocking tests
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_and_wake() {
|
fn block_and_wake() {
|
||||||
do with_test_task |mut task| {
|
do with_test_task |task| {
|
||||||
BlockedTask::try_block(task).unwrap_right().wake().unwrap()
|
BlockedTask::block(task).wake().unwrap()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn block_and_get_killed() {
|
|
||||||
do with_test_task |mut task| {
|
|
||||||
let mut handle = task.death.kill_handle.get_ref().clone();
|
|
||||||
let result = BlockedTask::try_block(task).unwrap_right();
|
|
||||||
let task = handle.kill().unwrap();
|
|
||||||
assert!(result.wake().is_none());
|
|
||||||
task
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn block_already_killed() {
|
|
||||||
do with_test_task |mut task| {
|
|
||||||
let mut handle = task.death.kill_handle.get_ref().clone();
|
|
||||||
assert!(handle.kill().is_none());
|
|
||||||
BlockedTask::try_block(task).unwrap_left()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn block_unkillably_and_get_killed() {
|
|
||||||
do with_test_task |mut task| {
|
|
||||||
let mut handle = task.death.kill_handle.get_ref().clone();
|
|
||||||
task.death.inhibit_kill(false);
|
|
||||||
let result = BlockedTask::try_block(task).unwrap_right();
|
|
||||||
assert!(handle.kill().is_none());
|
|
||||||
let mut task = result.wake().unwrap();
|
|
||||||
// This call wants to fail, but we can't have that happen since
|
|
||||||
// we're not running in a newsched task, so we can't even use
|
|
||||||
// spawntask_try. But the failing behaviour is already tested
|
|
||||||
// above, in unkillable_during_kill(), so we punt on it here.
|
|
||||||
task.death.allow_kill(true);
|
|
||||||
task
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn block_on_pipe() {
|
|
||||||
// Tests the "killable" path of casting to/from uint.
|
|
||||||
do run_in_newsched_task {
|
|
||||||
do with_test_task |mut task| {
|
|
||||||
let result = BlockedTask::try_block(task).unwrap_right();
|
|
||||||
let result = unsafe { result.cast_to_uint() };
|
|
||||||
let result = unsafe { BlockedTask::cast_from_uint(result) };
|
|
||||||
result.wake().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn block_unkillably_on_pipe() {
|
|
||||||
// Tests the "indestructible" path of casting to/from uint.
|
|
||||||
do run_in_newsched_task {
|
|
||||||
do with_test_task |mut task| {
|
|
||||||
task.death.inhibit_kill(false);
|
|
||||||
let result = BlockedTask::try_block(task).unwrap_right();
|
|
||||||
let result = unsafe { result.cast_to_uint() };
|
|
||||||
let result = unsafe { BlockedTask::cast_from_uint(result) };
|
|
||||||
let mut task = result.wake().unwrap();
|
|
||||||
task.death.allow_kill(false);
|
|
||||||
task
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ pub use self::util::set_exit_status;
|
|||||||
pub use self::util::default_sched_threads;
|
pub use self::util::default_sched_threads;
|
||||||
|
|
||||||
// Re-export of the functionality in the kill module
|
// Re-export of the functionality in the kill module
|
||||||
pub use self::kill::{KillHandle, BlockedTask};
|
pub use self::kill::BlockedTask;
|
||||||
|
|
||||||
// XXX: these probably shouldn't be public...
|
// XXX: these probably shouldn't be public...
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
use either::{Left, Right};
|
|
||||||
use option::{Option, Some, None};
|
use option::{Option, Some, None};
|
||||||
use cast::{transmute, transmute_mut_region, transmute_mut_unsafe};
|
use cast::{transmute, transmute_mut_region, transmute_mut_unsafe};
|
||||||
use clone::Clone;
|
use clone::Clone;
|
||||||
@ -621,9 +620,6 @@ impl Scheduler {
|
|||||||
unsafe {
|
unsafe {
|
||||||
let task: *mut Task = Local::unsafe_borrow();
|
let task: *mut Task = Local::unsafe_borrow();
|
||||||
(*task).sched.get_mut_ref().run_cleanup_job();
|
(*task).sched.get_mut_ref().run_cleanup_job();
|
||||||
|
|
||||||
// Must happen after running the cleanup job (of course).
|
|
||||||
(*task).death.check_killed((*task).unwinder.unwinding);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -689,14 +685,9 @@ impl Scheduler {
|
|||||||
pub fn switch_running_tasks_and_then(~self, next_task: ~Task,
|
pub fn switch_running_tasks_and_then(~self, next_task: ~Task,
|
||||||
f: |&mut Scheduler, BlockedTask|) {
|
f: |&mut Scheduler, BlockedTask|) {
|
||||||
// This is where we convert the BlockedTask-taking closure into one
|
// This is where we convert the BlockedTask-taking closure into one
|
||||||
// that takes just a Task, and is aware of the block-or-killed protocol.
|
// that takes just a Task
|
||||||
do self.change_task_context(next_task) |sched, task| {
|
do self.change_task_context(next_task) |sched, task| {
|
||||||
// Task might need to receive a kill signal instead of blocking.
|
f(sched, BlockedTask::block(task))
|
||||||
// We can call the "and_then" only if it blocks successfully.
|
|
||||||
match BlockedTask::try_block(task) {
|
|
||||||
Left(killed_task) => sched.enqueue_task(killed_task),
|
|
||||||
Right(blocked_task) => f(sched, blocked_task),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +36,6 @@ use rt::logging::StdErrLogger;
|
|||||||
use rt::sched::{Scheduler, SchedHandle};
|
use rt::sched::{Scheduler, SchedHandle};
|
||||||
use rt::stack::{StackSegment, StackPool};
|
use rt::stack::{StackSegment, StackPool};
|
||||||
use send_str::SendStr;
|
use send_str::SendStr;
|
||||||
use task::LinkedFailure;
|
|
||||||
use task::spawn::Taskgroup;
|
|
||||||
use unstable::finally::Finally;
|
use unstable::finally::Finally;
|
||||||
|
|
||||||
// The Task struct represents all state associated with a rust
|
// The Task struct represents all state associated with a rust
|
||||||
@ -52,7 +50,6 @@ pub struct Task {
|
|||||||
storage: LocalStorage,
|
storage: LocalStorage,
|
||||||
logger: Option<StdErrLogger>,
|
logger: Option<StdErrLogger>,
|
||||||
unwinder: Unwinder,
|
unwinder: Unwinder,
|
||||||
taskgroup: Option<Taskgroup>,
|
|
||||||
death: Death,
|
death: Death,
|
||||||
destroyed: bool,
|
destroyed: bool,
|
||||||
name: Option<SendStr>,
|
name: Option<SendStr>,
|
||||||
@ -188,7 +185,6 @@ impl Task {
|
|||||||
storage: LocalStorage(None),
|
storage: LocalStorage(None),
|
||||||
logger: None,
|
logger: None,
|
||||||
unwinder: Unwinder { unwinding: false, cause: None },
|
unwinder: Unwinder { unwinding: false, cause: None },
|
||||||
taskgroup: None,
|
|
||||||
death: Death::new(),
|
death: Death::new(),
|
||||||
destroyed: false,
|
destroyed: false,
|
||||||
coroutine: Some(Coroutine::empty()),
|
coroutine: Some(Coroutine::empty()),
|
||||||
@ -223,7 +219,6 @@ impl Task {
|
|||||||
storage: LocalStorage(None),
|
storage: LocalStorage(None),
|
||||||
logger: None,
|
logger: None,
|
||||||
unwinder: Unwinder { unwinding: false, cause: None },
|
unwinder: Unwinder { unwinding: false, cause: None },
|
||||||
taskgroup: None,
|
|
||||||
death: Death::new(),
|
death: Death::new(),
|
||||||
destroyed: false,
|
destroyed: false,
|
||||||
name: None,
|
name: None,
|
||||||
@ -246,9 +241,7 @@ impl Task {
|
|||||||
storage: LocalStorage(None),
|
storage: LocalStorage(None),
|
||||||
logger: None,
|
logger: None,
|
||||||
unwinder: Unwinder { unwinding: false, cause: None },
|
unwinder: Unwinder { unwinding: false, cause: None },
|
||||||
taskgroup: None,
|
death: Death::new(),
|
||||||
// FIXME(#7544) make watching optional
|
|
||||||
death: self.death.new_child(),
|
|
||||||
destroyed: false,
|
destroyed: false,
|
||||||
name: None,
|
name: None,
|
||||||
coroutine: Some(Coroutine::new(stack_pool, stack_size, start)),
|
coroutine: Some(Coroutine::new(stack_pool, stack_size, start)),
|
||||||
@ -333,11 +326,7 @@ impl Task {
|
|||||||
// Cleanup the dynamic borrowck debugging info
|
// Cleanup the dynamic borrowck debugging info
|
||||||
borrowck::clear_task_borrow_list();
|
borrowck::clear_task_borrow_list();
|
||||||
|
|
||||||
// NB. We pass the taskgroup into death so that it can be dropped while
|
self.death.collect_failure(self.unwinder.to_unwind_result());
|
||||||
// the unkillable counter is set. This is necessary for when the
|
|
||||||
// taskgroup destruction code drops references on KillHandles, which
|
|
||||||
// might require using unkillable (to synchronize with an unwrapper).
|
|
||||||
self.death.collect_failure(self.unwinder.to_unwind_result(), self.taskgroup.take());
|
|
||||||
self.destroyed = true;
|
self.destroyed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -660,11 +649,8 @@ pub fn begin_unwind<M: Any + Send>(msg: M, file: &'static str, line: uint) -> !
|
|||||||
Some(s) => *s,
|
Some(s) => *s,
|
||||||
None => match msg.as_ref::<~str>() {
|
None => match msg.as_ref::<~str>() {
|
||||||
Some(s) => s.as_slice(),
|
Some(s) => s.as_slice(),
|
||||||
None => match msg.as_ref::<LinkedFailure>() {
|
|
||||||
Some(*) => "linked failure",
|
|
||||||
None => "~Any",
|
None => "~Any",
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if !in_green_task_context() {
|
if !in_green_task_context() {
|
||||||
@ -785,16 +771,6 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn linked_failure() {
|
|
||||||
do run_in_newsched_task() {
|
|
||||||
let res = do spawntask_try {
|
|
||||||
spawntask_random(|| fail!());
|
|
||||||
};
|
|
||||||
assert!(res.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn heap_cycles() {
|
fn heap_cycles() {
|
||||||
use option::{Option, Some, None};
|
use option::{Option, Some, None};
|
||||||
|
@ -21,7 +21,6 @@ use io;
|
|||||||
use libc::{pid_t, c_int};
|
use libc::{pid_t, c_int};
|
||||||
use libc;
|
use libc;
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
use task;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A value representing a child process.
|
* A value representing a child process.
|
||||||
@ -221,13 +220,7 @@ impl Process {
|
|||||||
let ch = SharedChan::new(ch);
|
let ch = SharedChan::new(ch);
|
||||||
let ch_clone = ch.clone();
|
let ch_clone = ch.clone();
|
||||||
|
|
||||||
// FIXME(#910, #8674): right now I/O is incredibly brittle when it comes
|
do spawn {
|
||||||
// to linked failure, so these tasks must be spawn so they're not
|
|
||||||
// affected by linked failure. If these are removed, then the
|
|
||||||
// runtime may never exit because linked failure will cause some
|
|
||||||
// SchedHandle structures to not get destroyed, meaning that
|
|
||||||
// there's always an async watcher available.
|
|
||||||
do task::spawn_unlinked {
|
|
||||||
do io::ignore_io_error {
|
do io::ignore_io_error {
|
||||||
match error.take() {
|
match error.take() {
|
||||||
Some(ref mut e) => ch.send((2, e.read_to_end())),
|
Some(ref mut e) => ch.send((2, e.read_to_end())),
|
||||||
@ -235,7 +228,7 @@ impl Process {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
do task::spawn_unlinked {
|
do spawn {
|
||||||
do io::ignore_io_error {
|
do io::ignore_io_error {
|
||||||
match output.take() {
|
match output.take() {
|
||||||
Some(ref mut e) => ch_clone.send((1, e.read_to_end())),
|
Some(ref mut e) => ch_clone.send((1, e.read_to_end())),
|
||||||
|
@ -21,7 +21,6 @@ use rt::local::Local;
|
|||||||
use rt::rtio::EventLoop;
|
use rt::rtio::EventLoop;
|
||||||
use rt::sched::Scheduler;
|
use rt::sched::Scheduler;
|
||||||
use rt::shouldnt_be_public::{SelectInner, SelectPortInner};
|
use rt::shouldnt_be_public::{SelectInner, SelectPortInner};
|
||||||
use task;
|
|
||||||
use unstable::finally::Finally;
|
use unstable::finally::Finally;
|
||||||
use vec::{OwnedVector, MutableVector};
|
use vec::{OwnedVector, MutableVector};
|
||||||
|
|
||||||
@ -79,11 +78,10 @@ pub fn select<A: Select>(ports: &mut [A]) -> uint {
|
|||||||
do sched.event_loop.callback { c.take().send_deferred(()) }
|
do sched.event_loop.callback { c.take().send_deferred(()) }
|
||||||
}
|
}
|
||||||
}).finally {
|
}).finally {
|
||||||
let p = Cell::new(p.take());
|
|
||||||
// Unkillable is necessary not because getting killed is dangerous here,
|
// Unkillable is necessary not because getting killed is dangerous here,
|
||||||
// but to force the recv not to use the same kill-flag that we used for
|
// but to force the recv not to use the same kill-flag that we used for
|
||||||
// selecting. Otherwise a user-sender could spuriously wakeup us here.
|
// selecting. Otherwise a user-sender could spuriously wakeup us here.
|
||||||
do task::unkillable { p.take().recv(); }
|
p.take().recv();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Task resumes. Now unblock ourselves from all the ports we blocked on.
|
// Task resumes. Now unblock ourselves from all the ports we blocked on.
|
||||||
@ -230,9 +228,9 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_unkillable() {
|
fn select_simple() {
|
||||||
do run_in_uv_task {
|
do run_in_uv_task {
|
||||||
do task::unkillable { select_helper(2, [1]) }
|
select_helper(2, [1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,10 +238,6 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_blocking() {
|
fn select_blocking() {
|
||||||
select_blocking_helper(true);
|
|
||||||
select_blocking_helper(false);
|
|
||||||
|
|
||||||
fn select_blocking_helper(killable: bool) {
|
|
||||||
do run_in_uv_task {
|
do run_in_uv_task {
|
||||||
let (p1,_c) = oneshot();
|
let (p1,_c) = oneshot();
|
||||||
let (p2,c2) = oneshot();
|
let (p2,c2) = oneshot();
|
||||||
@ -264,12 +258,7 @@ mod test {
|
|||||||
// Try to block before child sends on c2.
|
// Try to block before child sends on c2.
|
||||||
c3.send(());
|
c3.send(());
|
||||||
p4.recv();
|
p4.recv();
|
||||||
if killable {
|
|
||||||
assert!(select(ports) == 1);
|
assert!(select(ports) == 1);
|
||||||
} else {
|
|
||||||
do task::unkillable { assert!(select(ports) == 1); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,16 +266,12 @@ mod test {
|
|||||||
fn select_racing_senders() {
|
fn select_racing_senders() {
|
||||||
static NUM_CHANS: uint = 10;
|
static NUM_CHANS: uint = 10;
|
||||||
|
|
||||||
select_racing_senders_helper(true, ~[0,1,2,3,4,5,6,7,8,9]);
|
select_racing_senders_helper(~[0,1,2,3,4,5,6,7,8,9]);
|
||||||
select_racing_senders_helper(false, ~[0,1,2,3,4,5,6,7,8,9]);
|
select_racing_senders_helper(~[0,1,2]);
|
||||||
select_racing_senders_helper(true, ~[0,1,2]);
|
select_racing_senders_helper(~[3,4,5,6]);
|
||||||
select_racing_senders_helper(false, ~[0,1,2]);
|
select_racing_senders_helper(~[7,8,9]);
|
||||||
select_racing_senders_helper(true, ~[3,4,5,6]);
|
|
||||||
select_racing_senders_helper(false, ~[3,4,5,6]);
|
|
||||||
select_racing_senders_helper(true, ~[7,8,9]);
|
|
||||||
select_racing_senders_helper(false, ~[7,8,9]);
|
|
||||||
|
|
||||||
fn select_racing_senders_helper(killable: bool, send_on_chans: ~[uint]) {
|
fn select_racing_senders_helper(send_on_chans: ~[uint]) {
|
||||||
use rt::test::spawntask_random;
|
use rt::test::spawntask_random;
|
||||||
|
|
||||||
do run_in_uv_task {
|
do run_in_uv_task {
|
||||||
@ -307,45 +292,10 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// nondeterministic result, but should succeed
|
// nondeterministic result, but should succeed
|
||||||
if killable {
|
|
||||||
select(ports);
|
select(ports);
|
||||||
} else {
|
|
||||||
do task::unkillable { select(ports); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn select_killed() {
|
|
||||||
do run_in_uv_task {
|
|
||||||
let (success_p, success_c) = oneshot::<bool>();
|
|
||||||
let success_c = Cell::new(success_c);
|
|
||||||
do task::try {
|
|
||||||
let success_c = Cell::new(success_c.take());
|
|
||||||
do task::unkillable {
|
|
||||||
let (p,c) = oneshot();
|
|
||||||
let c = Cell::new(c);
|
|
||||||
do task::spawn {
|
|
||||||
let (dead_ps, dead_cs) = unzip(range(0u, 5).map(|_| oneshot::<()>()));
|
|
||||||
let mut ports = dead_ps;
|
|
||||||
select(ports); // should get killed; nothing should leak
|
|
||||||
c.take().send(()); // must not happen
|
|
||||||
// Make sure dead_cs doesn't get closed until after select.
|
|
||||||
let _ = dead_cs;
|
|
||||||
}
|
|
||||||
do task::spawn {
|
|
||||||
fail!(); // should kill sibling awake
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for killed selector to close (NOT send on) its c.
|
|
||||||
// hope to send 'true'.
|
|
||||||
success_c.take().send(p.try_recv().is_none());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
assert!(success_p.recv());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -62,16 +62,12 @@ use rt::in_green_task_context;
|
|||||||
use rt::local::Local;
|
use rt::local::Local;
|
||||||
use rt::task::{UnwindResult, Success, Failure};
|
use rt::task::{UnwindResult, Success, Failure};
|
||||||
use send_str::{SendStr, IntoSendStr};
|
use send_str::{SendStr, IntoSendStr};
|
||||||
use unstable::finally::Finally;
|
|
||||||
use util;
|
use util;
|
||||||
|
|
||||||
#[cfg(test)] use any::Any;
|
#[cfg(test)] use any::Any;
|
||||||
#[cfg(test)] use cast;
|
|
||||||
#[cfg(test)] use comm::SharedChan;
|
#[cfg(test)] use comm::SharedChan;
|
||||||
#[cfg(test)] use comm;
|
|
||||||
#[cfg(test)] use ptr;
|
#[cfg(test)] use ptr;
|
||||||
#[cfg(test)] use result;
|
#[cfg(test)] use result;
|
||||||
#[cfg(test)] use task;
|
|
||||||
|
|
||||||
pub mod spawn;
|
pub mod spawn;
|
||||||
|
|
||||||
@ -86,8 +82,6 @@ pub mod spawn;
|
|||||||
/// children tasks complete, recommend using a result future.
|
/// children tasks complete, recommend using a result future.
|
||||||
pub type TaskResult = Result<(), ~Any>;
|
pub type TaskResult = Result<(), ~Any>;
|
||||||
|
|
||||||
pub struct LinkedFailure;
|
|
||||||
|
|
||||||
pub struct TaskResultPort {
|
pub struct TaskResultPort {
|
||||||
priv port: Port<UnwindResult>
|
priv port: Port<UnwindResult>
|
||||||
}
|
}
|
||||||
@ -141,24 +135,11 @@ pub struct SchedOpts {
|
|||||||
*
|
*
|
||||||
* # Fields
|
* # Fields
|
||||||
*
|
*
|
||||||
* * linked - Propagate failure bidirectionally between child and parent.
|
|
||||||
* True by default. If both this and 'supervised' are false, then
|
|
||||||
* either task's failure will not affect the other ("unlinked").
|
|
||||||
*
|
|
||||||
* * supervised - Propagate failure unidirectionally from parent to child,
|
|
||||||
* but not from child to parent. False by default.
|
|
||||||
*
|
|
||||||
* * watched - Make parent task collect exit status notifications from child
|
* * watched - Make parent task collect exit status notifications from child
|
||||||
* before reporting its own exit status. (This delays the parent
|
* before reporting its own exit status. (This delays the parent
|
||||||
* task's death and cleanup until after all transitively watched
|
* task's death and cleanup until after all transitively watched
|
||||||
* children also exit.) True by default.
|
* children also exit.) True by default.
|
||||||
*
|
*
|
||||||
* * indestructible - Configures the task to ignore kill signals received from
|
|
||||||
* linked failure. This may cause process hangs during
|
|
||||||
* failure if not used carefully, but causes task blocking
|
|
||||||
* code paths (e.g. port recv() calls) to be faster by 2
|
|
||||||
* atomic operations. False by default.
|
|
||||||
*
|
|
||||||
* * notify_chan - Enable lifecycle notifications on the given channel
|
* * notify_chan - Enable lifecycle notifications on the given channel
|
||||||
*
|
*
|
||||||
* * name - A name for the task-to-be, for identification in failure messages.
|
* * name - A name for the task-to-be, for identification in failure messages.
|
||||||
@ -169,10 +150,7 @@ pub struct SchedOpts {
|
|||||||
* scheduler other tasks will be impeded or even blocked indefinitely.
|
* scheduler other tasks will be impeded or even blocked indefinitely.
|
||||||
*/
|
*/
|
||||||
pub struct TaskOpts {
|
pub struct TaskOpts {
|
||||||
priv linked: bool,
|
|
||||||
priv supervised: bool,
|
|
||||||
priv watched: bool,
|
priv watched: bool,
|
||||||
priv indestructible: bool,
|
|
||||||
priv notify_chan: Option<Chan<UnwindResult>>,
|
priv notify_chan: Option<Chan<UnwindResult>>,
|
||||||
name: Option<SendStr>,
|
name: Option<SendStr>,
|
||||||
sched: SchedOpts,
|
sched: SchedOpts,
|
||||||
@ -191,13 +169,10 @@ pub struct TaskOpts {
|
|||||||
// when you try to reuse the builder to spawn a new task. We'll just
|
// when you try to reuse the builder to spawn a new task. We'll just
|
||||||
// sidestep that whole issue by making builders uncopyable and making
|
// sidestep that whole issue by making builders uncopyable and making
|
||||||
// the run function move them in.
|
// the run function move them in.
|
||||||
|
|
||||||
// FIXME (#3724): Replace the 'consumed' bit with move mode on self
|
|
||||||
pub struct TaskBuilder {
|
pub struct TaskBuilder {
|
||||||
opts: TaskOpts,
|
opts: TaskOpts,
|
||||||
priv gen_body: Option<proc(v: proc()) -> proc()>,
|
priv gen_body: Option<proc(v: proc()) -> proc()>,
|
||||||
priv can_not_copy: Option<util::NonCopyable>,
|
priv can_not_copy: Option<util::NonCopyable>,
|
||||||
priv consumed: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -210,25 +185,17 @@ pub fn task() -> TaskBuilder {
|
|||||||
opts: default_task_opts(),
|
opts: default_task_opts(),
|
||||||
gen_body: None,
|
gen_body: None,
|
||||||
can_not_copy: None,
|
can_not_copy: None,
|
||||||
consumed: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskBuilder {
|
impl TaskBuilder {
|
||||||
fn consume(&mut self) -> TaskBuilder {
|
fn consume(mut self) -> TaskBuilder {
|
||||||
if self.consumed {
|
|
||||||
fail!("Cannot copy a task_builder"); // Fake move mode on self
|
|
||||||
}
|
|
||||||
self.consumed = true;
|
|
||||||
let gen_body = self.gen_body.take();
|
let gen_body = self.gen_body.take();
|
||||||
let notify_chan = self.opts.notify_chan.take();
|
let notify_chan = self.opts.notify_chan.take();
|
||||||
let name = self.opts.name.take();
|
let name = self.opts.name.take();
|
||||||
TaskBuilder {
|
TaskBuilder {
|
||||||
opts: TaskOpts {
|
opts: TaskOpts {
|
||||||
linked: self.opts.linked,
|
|
||||||
supervised: self.opts.supervised,
|
|
||||||
watched: self.opts.watched,
|
watched: self.opts.watched,
|
||||||
indestructible: self.opts.indestructible,
|
|
||||||
notify_chan: notify_chan,
|
notify_chan: notify_chan,
|
||||||
name: name,
|
name: name,
|
||||||
sched: self.opts.sched,
|
sched: self.opts.sched,
|
||||||
@ -236,34 +203,9 @@ impl TaskBuilder {
|
|||||||
},
|
},
|
||||||
gen_body: gen_body,
|
gen_body: gen_body,
|
||||||
can_not_copy: None,
|
can_not_copy: None,
|
||||||
consumed: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decouple the child task's failure from the parent's. If either fails,
|
|
||||||
/// the other will not be killed.
|
|
||||||
pub fn unlinked(&mut self) {
|
|
||||||
self.opts.linked = false;
|
|
||||||
self.opts.watched = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unidirectionally link the child task's failure with the parent's. The
|
|
||||||
/// child's failure will not kill the parent, but the parent's will kill
|
|
||||||
/// the child.
|
|
||||||
pub fn supervised(&mut self) {
|
|
||||||
self.opts.supervised = true;
|
|
||||||
self.opts.linked = false;
|
|
||||||
self.opts.watched = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Link the child task's and parent task's failures. If either fails, the
|
|
||||||
/// other will be killed.
|
|
||||||
pub fn linked(&mut self) {
|
|
||||||
self.opts.linked = true;
|
|
||||||
self.opts.supervised = false;
|
|
||||||
self.opts.watched = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cause the parent task to collect the child's exit status (and that of
|
/// Cause the parent task to collect the child's exit status (and that of
|
||||||
/// all transitively-watched grandchildren) before reporting its own.
|
/// all transitively-watched grandchildren) before reporting its own.
|
||||||
pub fn watched(&mut self) {
|
pub fn watched(&mut self) {
|
||||||
@ -276,13 +218,6 @@ impl TaskBuilder {
|
|||||||
self.opts.watched = false;
|
self.opts.watched = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cause the child task to ignore any kill signals received from linked
|
|
||||||
/// failure. This optimizes context switching, at the possible expense of
|
|
||||||
/// process hangs in the case of unexpected failure.
|
|
||||||
pub fn indestructible(&mut self) {
|
|
||||||
self.opts.indestructible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a future representing the exit status of the task.
|
/// Get a future representing the exit status of the task.
|
||||||
///
|
///
|
||||||
/// Taking the value of the future will block until the child task
|
/// Taking the value of the future will block until the child task
|
||||||
@ -372,16 +307,13 @@ impl TaskBuilder {
|
|||||||
* When spawning into a new scheduler, the number of threads requested
|
* When spawning into a new scheduler, the number of threads requested
|
||||||
* must be greater than zero.
|
* must be greater than zero.
|
||||||
*/
|
*/
|
||||||
pub fn spawn(&mut self, f: proc()) {
|
pub fn spawn(mut self, f: proc()) {
|
||||||
let gen_body = self.gen_body.take();
|
let gen_body = self.gen_body.take();
|
||||||
let notify_chan = self.opts.notify_chan.take();
|
let notify_chan = self.opts.notify_chan.take();
|
||||||
let name = self.opts.name.take();
|
let name = self.opts.name.take();
|
||||||
let x = self.consume();
|
let x = self.consume();
|
||||||
let opts = TaskOpts {
|
let opts = TaskOpts {
|
||||||
linked: x.opts.linked,
|
|
||||||
supervised: x.opts.supervised,
|
|
||||||
watched: x.opts.watched,
|
watched: x.opts.watched,
|
||||||
indestructible: x.opts.indestructible,
|
|
||||||
notify_chan: notify_chan,
|
notify_chan: notify_chan,
|
||||||
name: name,
|
name: name,
|
||||||
sched: x.opts.sched,
|
sched: x.opts.sched,
|
||||||
@ -398,14 +330,6 @@ impl TaskBuilder {
|
|||||||
spawn::spawn_raw(opts, f);
|
spawn::spawn_raw(opts, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs a task, while transferring ownership of one argument to the child.
|
|
||||||
pub fn spawn_with<A:Send>(&mut self, arg: A, f: proc(v: A)) {
|
|
||||||
let arg = Cell::new(arg);
|
|
||||||
do self.spawn {
|
|
||||||
f(arg.take());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a function in another task and return either the return value
|
* Execute a function in another task and return either the return value
|
||||||
* of the function or result::err.
|
* of the function or result::err.
|
||||||
@ -419,7 +343,7 @@ impl TaskBuilder {
|
|||||||
* # Failure
|
* # Failure
|
||||||
* Fails if a future_result was already set for this task.
|
* Fails if a future_result was already set for this task.
|
||||||
*/
|
*/
|
||||||
pub fn try<T:Send>(&mut self, f: proc() -> T) -> Result<T, ~Any> {
|
pub fn try<T:Send>(mut self, f: proc() -> T) -> Result<T, ~Any> {
|
||||||
let (po, ch) = stream::<T>();
|
let (po, ch) = stream::<T>();
|
||||||
|
|
||||||
let result = self.future_result();
|
let result = self.future_result();
|
||||||
@ -447,10 +371,7 @@ pub fn default_task_opts() -> TaskOpts {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
TaskOpts {
|
TaskOpts {
|
||||||
linked: true,
|
|
||||||
supervised: false,
|
|
||||||
watched: true,
|
watched: true,
|
||||||
indestructible: false,
|
|
||||||
notify_chan: None,
|
notify_chan: None,
|
||||||
name: None,
|
name: None,
|
||||||
sched: SchedOpts {
|
sched: SchedOpts {
|
||||||
@ -469,56 +390,10 @@ pub fn default_task_opts() -> TaskOpts {
|
|||||||
///
|
///
|
||||||
/// This function is equivalent to `task().spawn(f)`.
|
/// This function is equivalent to `task().spawn(f)`.
|
||||||
pub fn spawn(f: proc()) {
|
pub fn spawn(f: proc()) {
|
||||||
let mut task = task();
|
let task = task();
|
||||||
task.spawn(f)
|
task.spawn(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a child task unlinked from the current one. If either this
|
|
||||||
/// task or the child task fails, the other will not be killed.
|
|
||||||
pub fn spawn_unlinked(f: proc()) {
|
|
||||||
let mut task = task();
|
|
||||||
task.unlinked();
|
|
||||||
task.spawn(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_supervised(f: proc()) {
|
|
||||||
/*!
|
|
||||||
* Creates a child task supervised by the current one. If the child
|
|
||||||
* task fails, the parent will not be killed, but if the parent fails,
|
|
||||||
* the child will be killed.
|
|
||||||
*/
|
|
||||||
|
|
||||||
let mut task = task();
|
|
||||||
task.supervised();
|
|
||||||
task.spawn(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a child task that cannot be killed by linked failure. This causes
|
|
||||||
/// its context-switch path to be faster by 2 atomic swap operations.
|
|
||||||
/// (Note that this convenience wrapper still uses linked-failure, so the
|
|
||||||
/// child's children will still be killable by the parent. For the fastest
|
|
||||||
/// possible spawn mode, use task::task().unlinked().indestructible().spawn.)
|
|
||||||
pub fn spawn_indestructible(f: proc()) {
|
|
||||||
let mut task = task();
|
|
||||||
task.indestructible();
|
|
||||||
task.spawn(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_with<A:Send>(arg: A, f: proc(v: A)) {
|
|
||||||
/*!
|
|
||||||
* Runs a task, while transferring ownership of one argument to the
|
|
||||||
* child.
|
|
||||||
*
|
|
||||||
* This is useful for transferring ownership of noncopyables to
|
|
||||||
* another task.
|
|
||||||
*
|
|
||||||
* This function is equivalent to `task().spawn_with(arg, f)`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
let mut task = task();
|
|
||||||
task.spawn_with(arg, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_sched(mode: SchedMode, f: proc()) {
|
pub fn spawn_sched(mode: SchedMode, f: proc()) {
|
||||||
/*!
|
/*!
|
||||||
* Creates a new task on a new or existing scheduler.
|
* Creates a new task on a new or existing scheduler.
|
||||||
@ -545,8 +420,7 @@ pub fn try<T:Send>(f: proc() -> T) -> Result<T, ~Any> {
|
|||||||
* This is equivalent to task().supervised().try.
|
* This is equivalent to task().supervised().try.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let mut task = task();
|
let task = task();
|
||||||
task.supervised();
|
|
||||||
task.try(f)
|
task.try(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -590,159 +464,6 @@ pub fn failing() -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporarily make the task unkillable
|
|
||||||
*
|
|
||||||
* # Example
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* do task::unkillable {
|
|
||||||
* // detach / deschedule / destroy must all be called together
|
|
||||||
* rustrt::rust_port_detach(po);
|
|
||||||
* // This must not result in the current task being killed
|
|
||||||
* task::deschedule();
|
|
||||||
* rustrt::rust_port_destroy(po);
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
pub fn unkillable<U>(f: || -> U) -> U {
|
|
||||||
use rt::task::Task;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
if in_green_task_context() {
|
|
||||||
// The inhibits/allows might fail and need to borrow the task.
|
|
||||||
let t: *mut Task = Local::unsafe_borrow();
|
|
||||||
do (|| {
|
|
||||||
(*t).death.inhibit_kill((*t).unwinder.unwinding);
|
|
||||||
f()
|
|
||||||
}).finally {
|
|
||||||
(*t).death.allow_kill((*t).unwinder.unwinding);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// FIXME(#3095): This should be an rtabort as soon as the scheduler
|
|
||||||
// no longer uses a workqueue implemented with an Exclusive.
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes killable a task marked as unkillable. This
|
|
||||||
* is meant to be used only nested in unkillable.
|
|
||||||
*
|
|
||||||
* # Example
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* do task::unkillable {
|
|
||||||
* do task::rekillable {
|
|
||||||
* // Task is killable
|
|
||||||
* }
|
|
||||||
* // Task is unkillable again
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
pub fn rekillable<U>(f: || -> U) -> U {
|
|
||||||
use rt::task::Task;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
if in_green_task_context() {
|
|
||||||
let t: *mut Task = Local::unsafe_borrow();
|
|
||||||
do (|| {
|
|
||||||
(*t).death.allow_kill((*t).unwinder.unwinding);
|
|
||||||
f()
|
|
||||||
}).finally {
|
|
||||||
(*t).death.inhibit_kill((*t).unwinder.unwinding);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// FIXME(#3095): As in unkillable().
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_kill_unkillable_task() {
|
|
||||||
use rt::test::*;
|
|
||||||
|
|
||||||
// Attempt to test that when a kill signal is received at the start of an
|
|
||||||
// unkillable section, 'unkillable' unwinds correctly. This is actually
|
|
||||||
// quite a difficult race to expose, as the kill has to happen on a second
|
|
||||||
// CPU, *after* the spawner is already switched-back-to (and passes the
|
|
||||||
// killed check at the start of its timeslice). As far as I know, it's not
|
|
||||||
// possible to make this race deterministic, or even more likely to happen.
|
|
||||||
do run_in_uv_task {
|
|
||||||
do task::try {
|
|
||||||
do task::spawn {
|
|
||||||
fail!();
|
|
||||||
}
|
|
||||||
do task::unkillable { }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore(cfg(windows))]
|
|
||||||
fn test_kill_rekillable_task() {
|
|
||||||
use rt::test::*;
|
|
||||||
|
|
||||||
// Tests that when a kill signal is received, 'rekillable' and
|
|
||||||
// 'unkillable' unwind correctly in conjunction with each other.
|
|
||||||
do run_in_uv_task {
|
|
||||||
do task::try {
|
|
||||||
do task::unkillable {
|
|
||||||
do task::rekillable {
|
|
||||||
do task::spawn {
|
|
||||||
fail!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_fail]
|
|
||||||
#[ignore(cfg(windows))]
|
|
||||||
fn test_rekillable_not_nested() {
|
|
||||||
do rekillable {
|
|
||||||
// This should fail before
|
|
||||||
// receiving anything since
|
|
||||||
// this block should be nested
|
|
||||||
// into a unkillable block.
|
|
||||||
deschedule();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore(cfg(windows))]
|
|
||||||
fn test_rekillable_nested_failure() {
|
|
||||||
|
|
||||||
let result = do task::try {
|
|
||||||
do unkillable {
|
|
||||||
do rekillable {
|
|
||||||
let (port,chan) = comm::stream();
|
|
||||||
do task::spawn { chan.send(()); fail!(); }
|
|
||||||
port.recv(); // wait for child to exist
|
|
||||||
port.recv(); // block forever, expect to get killed.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[test] #[should_fail] #[ignore(cfg(windows))]
|
|
||||||
fn test_cant_dup_task_builder() {
|
|
||||||
let mut builder = task();
|
|
||||||
builder.unlinked();
|
|
||||||
do builder.spawn {}
|
|
||||||
// FIXME(#3724): For now, this is a -runtime- failure, because we haven't
|
|
||||||
// got move mode on self. When 3724 is fixed, this test should fail to
|
|
||||||
// compile instead, and should go in tests/compile-fail.
|
|
||||||
do builder.spawn {} // b should have been consumed by the previous call
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following 8 tests test the following 2^3 combinations:
|
// The following 8 tests test the following 2^3 combinations:
|
||||||
// {un,}linked {un,}supervised failure propagation {up,down}wards.
|
// {un,}linked {un,}supervised failure propagation {up,down}wards.
|
||||||
|
|
||||||
@ -752,207 +473,6 @@ fn test_cant_dup_task_builder() {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn block_forever() { let (po, _ch) = stream::<()>(); po.recv(); }
|
fn block_forever() { let (po, _ch) = stream::<()>(); po.recv(); }
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_unlinked_unsup_no_fail_down() { // grandchild sends on a port
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
let (po, ch) = stream();
|
|
||||||
let ch = SharedChan::new(ch);
|
|
||||||
do spawn_unlinked {
|
|
||||||
let ch = ch.clone();
|
|
||||||
do spawn_unlinked {
|
|
||||||
// Give middle task a chance to fail-but-not-kill-us.
|
|
||||||
do 16.times { task::deschedule(); }
|
|
||||||
ch.send(()); // If killed first, grandparent hangs.
|
|
||||||
}
|
|
||||||
fail!(); // Shouldn't kill either (grand)parent or (grand)child.
|
|
||||||
}
|
|
||||||
po.recv();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_unlinked_unsup_no_fail_up() { // child unlinked fails
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
do spawn_unlinked { fail!(); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_unlinked_sup_no_fail_up() { // child unlinked fails
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
do spawn_supervised { fail!(); }
|
|
||||||
// Give child a chance to fail-but-not-kill-us.
|
|
||||||
do 16.times { task::deschedule(); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_unlinked_sup_fail_down() {
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
let result: Result<(), ~Any> = do try {
|
|
||||||
do spawn_supervised { block_forever(); }
|
|
||||||
fail!(); // Shouldn't leave a child hanging around.
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_linked_sup_fail_up() { // child fails; parent fails
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
let result: Result<(), ~Any> = do try {
|
|
||||||
// Unidirectional "parenting" shouldn't override bidirectional linked.
|
|
||||||
// We have to cheat with opts - the interface doesn't support them because
|
|
||||||
// they don't make sense (redundant with task().supervised()).
|
|
||||||
let mut b0 = task();
|
|
||||||
b0.opts.linked = true;
|
|
||||||
b0.opts.supervised = true;
|
|
||||||
|
|
||||||
do b0.spawn {
|
|
||||||
fail!();
|
|
||||||
}
|
|
||||||
block_forever(); // We should get punted awake
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_linked_sup_fail_down() { // parent fails; child fails
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
let result: Result<(), ~Any> = do try {
|
|
||||||
// We have to cheat with opts - the interface doesn't support them because
|
|
||||||
// they don't make sense (redundant with task().supervised()).
|
|
||||||
let mut b0 = task();
|
|
||||||
b0.opts.linked = true;
|
|
||||||
b0.opts.supervised = true;
|
|
||||||
do b0.spawn { block_forever(); }
|
|
||||||
fail!(); // *both* mechanisms would be wrong if this didn't kill the child
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_linked_unsup_fail_up() { // child fails; parent fails
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
let result: Result<(), ~Any> = do try {
|
|
||||||
// Default options are to spawn linked & unsupervised.
|
|
||||||
do spawn { fail!(); }
|
|
||||||
block_forever(); // We should get punted awake
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_linked_unsup_fail_down() { // parent fails; child fails
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
let result: Result<(), ~Any> = do try {
|
|
||||||
// Default options are to spawn linked & unsupervised.
|
|
||||||
do spawn { block_forever(); }
|
|
||||||
fail!();
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_linked_unsup_default_opts() { // parent fails; child fails
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
let result: Result<(), ~Any> = do try {
|
|
||||||
// Make sure the above test is the same as this one.
|
|
||||||
let mut builder = task();
|
|
||||||
builder.linked();
|
|
||||||
do builder.spawn { block_forever(); }
|
|
||||||
fail!();
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A couple bonus linked failure tests - testing for failure propagation even
|
|
||||||
// when the middle task exits successfully early before kill signals are sent.
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_failure_propagate_grandchild() {
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
let result: Result<(), ~Any> = do try {
|
|
||||||
// Middle task exits; does grandparent's failure propagate across the gap?
|
|
||||||
do spawn_supervised {
|
|
||||||
do spawn_supervised { block_forever(); }
|
|
||||||
}
|
|
||||||
do 16.times { task::deschedule(); }
|
|
||||||
fail!();
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_failure_propagate_secondborn() {
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
let result: Result<(), ~Any> = do try {
|
|
||||||
// First-born child exits; does parent's failure propagate to sibling?
|
|
||||||
do spawn_supervised {
|
|
||||||
do spawn { block_forever(); } // linked
|
|
||||||
}
|
|
||||||
do 16.times { task::deschedule(); }
|
|
||||||
fail!();
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_failure_propagate_nephew_or_niece() {
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
let result: Result<(), ~Any> = do try {
|
|
||||||
// Our sibling exits; does our failure propagate to sibling's child?
|
|
||||||
do spawn { // linked
|
|
||||||
do spawn_supervised { block_forever(); }
|
|
||||||
}
|
|
||||||
do 16.times { task::deschedule(); }
|
|
||||||
fail!();
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_linked_sup_propagate_sibling() {
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
let result: Result<(), ~Any> = do try {
|
|
||||||
// Middle sibling exits - does eldest's failure propagate to youngest?
|
|
||||||
do spawn { // linked
|
|
||||||
do spawn { block_forever(); } // linked
|
|
||||||
}
|
|
||||||
do 16.times { task::deschedule(); }
|
|
||||||
fail!();
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unnamed_task() {
|
fn test_unnamed_task() {
|
||||||
use rt::test::run_in_uv_task;
|
use rt::test::run_in_uv_task;
|
||||||
@ -1014,7 +534,7 @@ fn test_send_named_task() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_run_basic() {
|
fn test_run_basic() {
|
||||||
let (po, ch) = stream::<()>();
|
let (po, ch) = stream::<()>();
|
||||||
let mut builder = task();
|
let builder = task();
|
||||||
do builder.spawn {
|
do builder.spawn {
|
||||||
ch.send(());
|
ch.send(());
|
||||||
}
|
}
|
||||||
@ -1053,7 +573,6 @@ fn test_future_result() {
|
|||||||
|
|
||||||
let mut builder = task();
|
let mut builder = task();
|
||||||
let result = builder.future_result();
|
let result = builder.future_result();
|
||||||
builder.unlinked();
|
|
||||||
do builder.spawn {
|
do builder.spawn {
|
||||||
fail!();
|
fail!();
|
||||||
}
|
}
|
||||||
@ -1224,7 +743,7 @@ fn test_avoid_copying_the_body_spawn() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_avoid_copying_the_body_task_spawn() {
|
fn test_avoid_copying_the_body_task_spawn() {
|
||||||
do avoid_copying_the_body |f| {
|
do avoid_copying_the_body |f| {
|
||||||
let mut builder = task();
|
let builder = task();
|
||||||
do builder.spawn || {
|
do builder.spawn || {
|
||||||
f();
|
f();
|
||||||
}
|
}
|
||||||
@ -1240,86 +759,6 @@ fn test_avoid_copying_the_body_try() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_avoid_copying_the_body_unlinked() {
|
|
||||||
do avoid_copying_the_body |f| {
|
|
||||||
do spawn_unlinked || {
|
|
||||||
f();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
#[should_fail]
|
|
||||||
fn test_unkillable() {
|
|
||||||
let (po, ch) = stream();
|
|
||||||
|
|
||||||
// We want to do this after failing
|
|
||||||
do spawn_unlinked {
|
|
||||||
do 10.times { deschedule() }
|
|
||||||
ch.send(());
|
|
||||||
}
|
|
||||||
|
|
||||||
do spawn {
|
|
||||||
deschedule();
|
|
||||||
// We want to fail after the unkillable task
|
|
||||||
// blocks on recv
|
|
||||||
fail!();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
do unkillable {
|
|
||||||
let p = ~0;
|
|
||||||
let pp: *uint = cast::transmute(p);
|
|
||||||
|
|
||||||
// If we are killed here then the box will leak
|
|
||||||
po.recv();
|
|
||||||
|
|
||||||
let _p: ~int = cast::transmute(pp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we can be killed
|
|
||||||
po.recv();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
#[should_fail]
|
|
||||||
fn test_unkillable_nested() {
|
|
||||||
let (po, ch) = comm::stream();
|
|
||||||
|
|
||||||
// We want to do this after failing
|
|
||||||
do spawn_unlinked || {
|
|
||||||
do 10.times { deschedule() }
|
|
||||||
ch.send(());
|
|
||||||
}
|
|
||||||
|
|
||||||
do spawn {
|
|
||||||
deschedule();
|
|
||||||
// We want to fail after the unkillable task
|
|
||||||
// blocks on recv
|
|
||||||
fail!();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
do unkillable {
|
|
||||||
do unkillable {} // Here's the difference from the previous test.
|
|
||||||
let p = ~0;
|
|
||||||
let pp: *uint = cast::transmute(p);
|
|
||||||
|
|
||||||
// If we are killed here then the box will leak
|
|
||||||
po.recv();
|
|
||||||
|
|
||||||
let _p: ~int = cast::transmute(pp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we can be killed
|
|
||||||
po.recv();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_child_doesnt_ref_parent() {
|
fn test_child_doesnt_ref_parent() {
|
||||||
// If the child refcounts the parent task, this will stack overflow when
|
// If the child refcounts the parent task, this will stack overflow when
|
||||||
@ -1350,67 +789,6 @@ fn test_simple_newsched_spawn() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_spawn_watched() {
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
let result = do try {
|
|
||||||
let mut t = task();
|
|
||||||
t.unlinked();
|
|
||||||
t.watched();
|
|
||||||
do t.spawn {
|
|
||||||
let mut t = task();
|
|
||||||
t.unlinked();
|
|
||||||
t.watched();
|
|
||||||
do t.spawn {
|
|
||||||
task::deschedule();
|
|
||||||
fail!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_indestructible() {
|
|
||||||
use rt::test::run_in_uv_task;
|
|
||||||
do run_in_uv_task {
|
|
||||||
let result = do try {
|
|
||||||
let mut t = task();
|
|
||||||
t.watched();
|
|
||||||
t.supervised();
|
|
||||||
t.indestructible();
|
|
||||||
do t.spawn {
|
|
||||||
let (p1, _c1) = stream::<()>();
|
|
||||||
let (p2, c2) = stream::<()>();
|
|
||||||
let (p3, c3) = stream::<()>();
|
|
||||||
let mut t = task();
|
|
||||||
t.unwatched();
|
|
||||||
do t.spawn {
|
|
||||||
do (|| {
|
|
||||||
p1.recv(); // would deadlock if not killed
|
|
||||||
}).finally {
|
|
||||||
c2.send(());
|
|
||||||
};
|
|
||||||
}
|
|
||||||
let mut t = task();
|
|
||||||
t.unwatched();
|
|
||||||
do t.spawn {
|
|
||||||
p3.recv();
|
|
||||||
task::deschedule();
|
|
||||||
fail!();
|
|
||||||
}
|
|
||||||
c3.send(());
|
|
||||||
p2.recv();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
assert!(result.is_ok());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_try_fail_message_static_str() {
|
fn test_try_fail_message_static_str() {
|
||||||
match do try {
|
match do try {
|
||||||
@ -1455,19 +833,6 @@ fn test_try_fail_message_any() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ignore(reason = "linked failure")]
|
|
||||||
#[test]
|
|
||||||
fn test_try_fail_message_linked() {
|
|
||||||
match do try {
|
|
||||||
do spawn {
|
|
||||||
fail!()
|
|
||||||
}
|
|
||||||
} {
|
|
||||||
Err(ref e) if e.is::<LinkedFailure>() => {}
|
|
||||||
Err(_) | Ok(()) => fail!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_try_fail_message_unit_struct() {
|
fn test_try_fail_message_unit_struct() {
|
||||||
struct Juju;
|
struct Juju;
|
||||||
|
@ -9,6 +9,10 @@
|
|||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
/*!**************************************************************************
|
/*!**************************************************************************
|
||||||
|
*
|
||||||
|
* WARNING: linked failure has been removed since this doc comment was written,
|
||||||
|
* but it was so pretty that I didn't want to remove it.
|
||||||
|
*
|
||||||
* Spawning & linked failure
|
* Spawning & linked failure
|
||||||
*
|
*
|
||||||
* Several data structures are involved in task management to allow properly
|
* Several data structures are involved in task management to allow properly
|
||||||
@ -73,541 +77,30 @@
|
|||||||
|
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
|
|
||||||
use cast::transmute;
|
|
||||||
use cast;
|
|
||||||
use cell::Cell;
|
use cell::Cell;
|
||||||
use comm::{Chan, GenericChan, oneshot};
|
use comm::{GenericChan, oneshot};
|
||||||
use container::MutableMap;
|
|
||||||
use hashmap::{HashSet, HashSetMoveIterator};
|
|
||||||
use local_data;
|
|
||||||
use rt::local::Local;
|
use rt::local::Local;
|
||||||
use rt::sched::{Scheduler, Shutdown, TaskFromFriend};
|
use rt::sched::{Scheduler, Shutdown, TaskFromFriend};
|
||||||
use rt::task::{Task, Sched};
|
use rt::task::{Task, Sched};
|
||||||
use rt::task::{UnwindResult, Success, Failure};
|
use rt::task::UnwindResult;
|
||||||
use rt::thread::Thread;
|
use rt::thread::Thread;
|
||||||
use rt::work_queue::WorkQueue;
|
use rt::work_queue::WorkQueue;
|
||||||
use rt::{in_green_task_context, new_event_loop, KillHandle};
|
use rt::{in_green_task_context, new_event_loop};
|
||||||
use task::LinkedFailure;
|
|
||||||
use task::SingleThreaded;
|
use task::SingleThreaded;
|
||||||
use task::TaskOpts;
|
use task::TaskOpts;
|
||||||
use task::unkillable;
|
|
||||||
use uint;
|
|
||||||
use unstable::sync::Exclusive;
|
|
||||||
use util;
|
|
||||||
|
|
||||||
#[cfg(test)] use task::default_task_opts;
|
#[cfg(test)] use task::default_task_opts;
|
||||||
#[cfg(test)] use comm;
|
#[cfg(test)] use comm;
|
||||||
#[cfg(test)] use task;
|
#[cfg(test)] use task;
|
||||||
|
|
||||||
struct TaskSet(HashSet<KillHandle>);
|
|
||||||
|
|
||||||
impl TaskSet {
|
|
||||||
#[inline]
|
|
||||||
fn new() -> TaskSet {
|
|
||||||
TaskSet(HashSet::new())
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn insert(&mut self, task: KillHandle) {
|
|
||||||
let didnt_overwrite = (**self).insert(task);
|
|
||||||
assert!(didnt_overwrite);
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn remove(&mut self, task: &KillHandle) {
|
|
||||||
let was_present = (**self).remove(task);
|
|
||||||
assert!(was_present);
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn move_iter(self) -> HashSetMoveIterator<KillHandle> {
|
|
||||||
(*self).move_iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// One of these per group of linked-failure tasks.
|
|
||||||
struct TaskGroupData {
|
|
||||||
// All tasks which might kill this group. When this is empty, the group
|
|
||||||
// can be "GC"ed (i.e., its link in the ancestor list can be removed).
|
|
||||||
members: TaskSet,
|
|
||||||
// All tasks unidirectionally supervised by (directly or transitively)
|
|
||||||
// tasks in this group.
|
|
||||||
descendants: TaskSet,
|
|
||||||
}
|
|
||||||
type TaskGroupArc = Exclusive<Option<TaskGroupData>>;
|
|
||||||
|
|
||||||
type TaskGroupInner<'self> = &'self mut Option<TaskGroupData>;
|
|
||||||
|
|
||||||
// A taskgroup is 'dead' when nothing can cause it to fail; only members can.
|
|
||||||
fn taskgroup_is_dead(tg: &TaskGroupData) -> bool {
|
|
||||||
tg.members.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
// A list-like structure by which taskgroups keep track of all ancestor groups
|
|
||||||
// which may kill them. Needed for tasks to be able to remove themselves from
|
|
||||||
// ancestor groups upon exit. The list has a node for each "generation", and
|
|
||||||
// ends either at the root taskgroup (which has no ancestors) or at a
|
|
||||||
// taskgroup which was spawned-unlinked. Tasks from intermediate generations
|
|
||||||
// have references to the middle of the list; when intermediate generations
|
|
||||||
// die, their node in the list will be collected at a descendant's spawn-time.
|
|
||||||
struct AncestorNode {
|
|
||||||
// Since the ancestor list is recursive, we end up with references to
|
|
||||||
// exclusives within other exclusives. This is dangerous business (if
|
|
||||||
// circular references arise, deadlock and memory leaks are imminent).
|
|
||||||
// Hence we assert that this counter monotonically decreases as we
|
|
||||||
// approach the tail of the list.
|
|
||||||
generation: uint,
|
|
||||||
// Handle to the tasks in the group of the current generation.
|
|
||||||
parent_group: TaskGroupArc,
|
|
||||||
// Recursive rest of the list.
|
|
||||||
ancestors: AncestorList,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AncestorList(Option<Exclusive<AncestorNode>>);
|
|
||||||
|
|
||||||
// Accessors for taskgroup arcs and ancestor arcs that wrap the unsafety.
|
|
||||||
#[inline]
|
|
||||||
fn access_group<U>(x: &TaskGroupArc, blk: |TaskGroupInner| -> U) -> U {
|
|
||||||
unsafe {
|
|
||||||
x.with(blk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn access_ancestors<U>(
|
|
||||||
x: &Exclusive<AncestorNode>,
|
|
||||||
blk: |x: &mut AncestorNode| -> U)
|
|
||||||
-> U {
|
|
||||||
unsafe {
|
|
||||||
x.with(blk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline] #[cfg(test)]
|
|
||||||
fn check_generation(younger: uint, older: uint) { assert!(younger > older); }
|
|
||||||
#[inline] #[cfg(not(test))]
|
|
||||||
fn check_generation(_younger: uint, _older: uint) { }
|
|
||||||
|
|
||||||
#[inline] #[cfg(test)]
|
|
||||||
fn incr_generation(ancestors: &AncestorList) -> uint {
|
|
||||||
ancestors.as_ref().map_default(0, |arc| access_ancestors(arc, |a| a.generation+1))
|
|
||||||
}
|
|
||||||
#[inline] #[cfg(not(test))]
|
|
||||||
fn incr_generation(_ancestors: &AncestorList) -> uint { 0 }
|
|
||||||
|
|
||||||
// Iterates over an ancestor list.
|
|
||||||
// (1) Runs forward_blk on each ancestral taskgroup in the list
|
|
||||||
// (2) If forward_blk "break"s, runs optional bail_blk on all ancestral
|
|
||||||
// taskgroups that forward_blk already ran on successfully (Note: bail_blk
|
|
||||||
// is NOT called on the block that forward_blk broke on!).
|
|
||||||
// (3) As a bonus, coalesces away all 'dead' taskgroup nodes in the list.
|
|
||||||
fn each_ancestor(list: &mut AncestorList,
|
|
||||||
bail_blk: |TaskGroupInner|,
|
|
||||||
forward_blk: |TaskGroupInner| -> bool)
|
|
||||||
-> bool {
|
|
||||||
// "Kickoff" call - there was no last generation.
|
|
||||||
return !coalesce(list, bail_blk, forward_blk, uint::max_value);
|
|
||||||
|
|
||||||
// Recursively iterates, and coalesces afterwards if needed. Returns
|
|
||||||
// whether or not unwinding is needed (i.e., !successful iteration).
|
|
||||||
fn coalesce(list: &mut AncestorList,
|
|
||||||
bail_blk: |TaskGroupInner|,
|
|
||||||
forward_blk: |TaskGroupInner| -> bool,
|
|
||||||
last_generation: uint) -> bool {
|
|
||||||
let (coalesce_this, early_break) =
|
|
||||||
iterate(list, bail_blk, forward_blk, last_generation);
|
|
||||||
// What should our next ancestor end up being?
|
|
||||||
if coalesce_this.is_some() {
|
|
||||||
// Needed coalesce. Our next ancestor becomes our old
|
|
||||||
// ancestor's next ancestor. ("next = old_next->next;")
|
|
||||||
*list = coalesce_this.unwrap();
|
|
||||||
}
|
|
||||||
return early_break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns an optional list-to-coalesce and whether unwinding is needed.
|
|
||||||
// Option<ancestor_list>:
|
|
||||||
// Whether or not the ancestor taskgroup being iterated over is
|
|
||||||
// dead or not; i.e., it has no more tasks left in it, whether or not
|
|
||||||
// it has descendants. If dead, the caller shall coalesce it away.
|
|
||||||
// bool:
|
|
||||||
// True if the supplied block did 'break', here or in any recursive
|
|
||||||
// calls. If so, must call the unwinder on all previous nodes.
|
|
||||||
fn iterate(ancestors: &mut AncestorList,
|
|
||||||
bail_blk: |TaskGroupInner|,
|
|
||||||
forward_blk: |TaskGroupInner| -> bool,
|
|
||||||
last_generation: uint)
|
|
||||||
-> (Option<AncestorList>, bool) {
|
|
||||||
// At each step of iteration, three booleans are at play which govern
|
|
||||||
// how the iteration should behave.
|
|
||||||
// 'nobe_is_dead' - Should the list should be coalesced at this point?
|
|
||||||
// Largely unrelated to the other two.
|
|
||||||
// 'need_unwind' - Should we run the bail_blk at this point? (i.e.,
|
|
||||||
// do_continue was false not here, but down the line)
|
|
||||||
// 'do_continue' - Did the forward_blk succeed at this point? (i.e.,
|
|
||||||
// should we recurse? or should our callers unwind?)
|
|
||||||
|
|
||||||
let forward_blk = Cell::new(forward_blk);
|
|
||||||
|
|
||||||
// The map defaults to None, because if ancestors is None, we're at
|
|
||||||
// the end of the list, which doesn't make sense to coalesce.
|
|
||||||
do ancestors.as_ref().map_default((None,false)) |ancestor_arc| {
|
|
||||||
// NB: Takes a lock! (this ancestor node)
|
|
||||||
do access_ancestors(ancestor_arc) |nobe| {
|
|
||||||
// Argh, but we couldn't give it to coalesce() otherwise.
|
|
||||||
let forward_blk = forward_blk.take();
|
|
||||||
// Check monotonicity
|
|
||||||
check_generation(last_generation, nobe.generation);
|
|
||||||
/*##########################################################*
|
|
||||||
* Step 1: Look at this ancestor group (call iterator block).
|
|
||||||
*##########################################################*/
|
|
||||||
let mut nobe_is_dead = false;
|
|
||||||
let do_continue =
|
|
||||||
// NB: Takes a lock! (this ancestor node's parent group)
|
|
||||||
do access_group(&nobe.parent_group) |tg_opt| {
|
|
||||||
// Decide whether this group is dead. Note that the
|
|
||||||
// group being *dead* is disjoint from it *failing*.
|
|
||||||
nobe_is_dead = match *tg_opt {
|
|
||||||
Some(ref tg) => taskgroup_is_dead(tg),
|
|
||||||
None => nobe_is_dead
|
|
||||||
};
|
|
||||||
// Call iterator block. (If the group is dead, it's
|
|
||||||
// safe to skip it. This will leave our KillHandle
|
|
||||||
// hanging around in the group even after it's freed,
|
|
||||||
// but that's ok because, by virtue of the group being
|
|
||||||
// dead, nobody will ever kill-all (for) over it.)
|
|
||||||
if nobe_is_dead { true } else { forward_blk(tg_opt) }
|
|
||||||
};
|
|
||||||
/*##########################################################*
|
|
||||||
* Step 2: Recurse on the rest of the list; maybe coalescing.
|
|
||||||
*##########################################################*/
|
|
||||||
// 'need_unwind' is only set if blk returned true above, *and*
|
|
||||||
// the recursive call early-broke.
|
|
||||||
let mut need_unwind = false;
|
|
||||||
if do_continue {
|
|
||||||
// NB: Takes many locks! (ancestor nodes & parent groups)
|
|
||||||
need_unwind = coalesce(&mut nobe.ancestors, |tg| bail_blk(tg),
|
|
||||||
forward_blk, nobe.generation);
|
|
||||||
}
|
|
||||||
/*##########################################################*
|
|
||||||
* Step 3: Maybe unwind; compute return info for our caller.
|
|
||||||
*##########################################################*/
|
|
||||||
if need_unwind && !nobe_is_dead {
|
|
||||||
do access_group(&nobe.parent_group) |tg_opt| {
|
|
||||||
bail_blk(tg_opt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Decide whether our caller should unwind.
|
|
||||||
need_unwind = need_unwind || !do_continue;
|
|
||||||
// Tell caller whether or not to coalesce and/or unwind
|
|
||||||
if nobe_is_dead {
|
|
||||||
// Swap the list out here; the caller replaces us with it.
|
|
||||||
let rest = util::replace(&mut nobe.ancestors,
|
|
||||||
AncestorList(None));
|
|
||||||
(Some(rest), need_unwind)
|
|
||||||
} else {
|
|
||||||
(None, need_unwind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// One of these per task.
|
|
||||||
pub struct Taskgroup {
|
|
||||||
// List of tasks with whose fates this one's is intertwined.
|
|
||||||
priv tasks: TaskGroupArc, // 'none' means the group has failed.
|
|
||||||
// Lists of tasks who will kill us if they fail, but whom we won't kill.
|
|
||||||
priv ancestors: AncestorList,
|
|
||||||
priv notifier: Option<AutoNotify>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Taskgroup {
|
|
||||||
// Runs on task exit.
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// If we are failing, the whole taskgroup needs to die.
|
|
||||||
do RuntimeGlue::with_task_handle_and_failing |me, failing| {
|
|
||||||
if failing {
|
|
||||||
for x in self.notifier.mut_iter() {
|
|
||||||
x.task_result = Some(Failure(~LinkedFailure as ~Any));
|
|
||||||
}
|
|
||||||
// Take everybody down with us. After this point, every
|
|
||||||
// other task in the group will see 'tg' as none, which
|
|
||||||
// indicates the whole taskgroup is failing (and forbids
|
|
||||||
// new spawns from succeeding).
|
|
||||||
let tg = do access_group(&self.tasks) |tg| { tg.take() };
|
|
||||||
// It's safe to send kill signals outside the lock, because
|
|
||||||
// we have a refcount on all kill-handles in the group.
|
|
||||||
kill_taskgroup(tg, me);
|
|
||||||
} else {
|
|
||||||
// Remove ourselves from the group(s).
|
|
||||||
do access_group(&self.tasks) |tg| {
|
|
||||||
leave_taskgroup(tg, me, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// It doesn't matter whether this happens before or after dealing
|
|
||||||
// with our own taskgroup, so long as both happen before we die.
|
|
||||||
// We remove ourself from every ancestor we can, so no cleanup; no
|
|
||||||
// break.
|
|
||||||
do each_ancestor(&mut self.ancestors, |_| {}) |ancestor_group| {
|
|
||||||
leave_taskgroup(ancestor_group, me, false);
|
|
||||||
true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Taskgroup(tasks: TaskGroupArc,
|
|
||||||
ancestors: AncestorList,
|
|
||||||
mut notifier: Option<AutoNotify>) -> Taskgroup {
|
|
||||||
for x in notifier.mut_iter() {
|
|
||||||
x.task_result = Some(Success);
|
|
||||||
}
|
|
||||||
|
|
||||||
Taskgroup {
|
|
||||||
tasks: tasks,
|
|
||||||
ancestors: ancestors,
|
|
||||||
notifier: notifier
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AutoNotify {
|
|
||||||
notify_chan: Chan<UnwindResult>,
|
|
||||||
|
|
||||||
// XXX: By value self drop would allow this to be a plain UnwindResult
|
|
||||||
task_result: Option<UnwindResult>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AutoNotify {
|
|
||||||
pub fn new(chan: Chan<UnwindResult>) -> AutoNotify {
|
|
||||||
AutoNotify {
|
|
||||||
notify_chan: chan,
|
|
||||||
|
|
||||||
// Un-set above when taskgroup successfully made.
|
|
||||||
task_result: Some(Failure(~("AutoNotify::new()") as ~Any))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for AutoNotify {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let result = self.task_result.take_unwrap();
|
|
||||||
|
|
||||||
self.notify_chan.send(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enlist_in_taskgroup(state: TaskGroupInner, me: KillHandle,
|
|
||||||
is_member: bool) -> bool {
|
|
||||||
let me = Cell::new(me); // :(
|
|
||||||
// If 'None', the group was failing. Can't enlist.
|
|
||||||
do state.as_mut().map_default(false) |group| {
|
|
||||||
(if is_member {
|
|
||||||
&mut group.members
|
|
||||||
} else {
|
|
||||||
&mut group.descendants
|
|
||||||
}).insert(me.take());
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NB: Runs in destructor/post-exit context. Can't 'fail'.
|
|
||||||
fn leave_taskgroup(state: TaskGroupInner, me: &KillHandle, is_member: bool) {
|
|
||||||
let me = Cell::new(me); // :(
|
|
||||||
// If 'None', already failing and we've already gotten a kill signal.
|
|
||||||
do state.as_mut().map |group| {
|
|
||||||
(if is_member {
|
|
||||||
&mut group.members
|
|
||||||
} else {
|
|
||||||
&mut group.descendants
|
|
||||||
}).remove(me.take());
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// NB: Runs in destructor/post-exit context. Can't 'fail'.
|
|
||||||
fn kill_taskgroup(state: Option<TaskGroupData>, me: &KillHandle) {
|
|
||||||
// Might already be None, if somebody is failing simultaneously.
|
|
||||||
// That's ok; only one task needs to do the dirty work. (Might also
|
|
||||||
// see 'None' if somebody already failed and we got a kill signal.)
|
|
||||||
do state.map |TaskGroupData { members: members, descendants: descendants }| {
|
|
||||||
for sibling in members.move_iter() {
|
|
||||||
// Skip self - killing ourself won't do much good.
|
|
||||||
if &sibling != me {
|
|
||||||
RuntimeGlue::kill_task(sibling);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for child in descendants.move_iter() {
|
|
||||||
assert!(&child != me);
|
|
||||||
RuntimeGlue::kill_task(child);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// (note: multiple tasks may reach this point)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME (#2912): Work around core-vs-coretest function duplication. Can't use
|
|
||||||
// a proper closure because the #[test]s won't understand. Have to fake it.
|
|
||||||
fn taskgroup_key() -> local_data::Key<@@mut Taskgroup> {
|
|
||||||
unsafe { cast::transmute(-2) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transitionary.
|
|
||||||
struct RuntimeGlue;
|
|
||||||
impl RuntimeGlue {
|
|
||||||
fn kill_task(mut handle: KillHandle) {
|
|
||||||
do handle.kill().map |killed_task| {
|
|
||||||
let killed_task = Cell::new(killed_task);
|
|
||||||
do Local::borrow |sched: &mut Scheduler| {
|
|
||||||
sched.enqueue_task(killed_task.take());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_task_handle_and_failing(blk: |&KillHandle, bool|) {
|
|
||||||
assert!(in_green_task_context());
|
|
||||||
unsafe {
|
|
||||||
// Can't use safe borrow, because the taskgroup destructor needs to
|
|
||||||
// access the scheduler again to send kill signals to other tasks.
|
|
||||||
let me: *mut Task = Local::unsafe_borrow();
|
|
||||||
blk((*me).death.kill_handle.get_ref(), (*me).unwinder.unwinding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_my_taskgroup<U>(blk: |&Taskgroup| -> U) -> U {
|
|
||||||
assert!(in_green_task_context());
|
|
||||||
unsafe {
|
|
||||||
// Can't use safe borrow, because creating new hashmaps for the
|
|
||||||
// tasksets requires an rng, which needs to borrow the sched.
|
|
||||||
let me: *mut Task = Local::unsafe_borrow();
|
|
||||||
blk(match (*me).taskgroup {
|
|
||||||
None => {
|
|
||||||
// First task in its (unlinked/unsupervised) taskgroup.
|
|
||||||
// Lazily initialize.
|
|
||||||
let mut members = TaskSet::new();
|
|
||||||
let my_handle = (*me).death.kill_handle.get_ref().clone();
|
|
||||||
members.insert(my_handle);
|
|
||||||
let tasks = Exclusive::new(Some(TaskGroupData {
|
|
||||||
members: members,
|
|
||||||
descendants: TaskSet::new(),
|
|
||||||
}));
|
|
||||||
let group = Taskgroup(tasks, AncestorList(None), None);
|
|
||||||
(*me).taskgroup = Some(group);
|
|
||||||
(*me).taskgroup.get_ref()
|
|
||||||
}
|
|
||||||
Some(ref group) => group,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns 'None' in the case where the child's TG should be lazily initialized.
|
|
||||||
fn gen_child_taskgroup(linked: bool, supervised: bool)
|
|
||||||
-> Option<(TaskGroupArc, AncestorList)> {
|
|
||||||
if linked || supervised {
|
|
||||||
// with_my_taskgroup will lazily initialize the parent's taskgroup if
|
|
||||||
// it doesn't yet exist. We don't want to call it in the unlinked case.
|
|
||||||
do RuntimeGlue::with_my_taskgroup |spawner_group| {
|
|
||||||
let ancestors = AncestorList(spawner_group.ancestors.as_ref().map(|x| x.clone()));
|
|
||||||
if linked {
|
|
||||||
// Child is in the same group as spawner.
|
|
||||||
// Child's ancestors are spawner's ancestors.
|
|
||||||
Some((spawner_group.tasks.clone(), ancestors))
|
|
||||||
} else {
|
|
||||||
// Child is in a separate group from spawner.
|
|
||||||
let g = Exclusive::new(Some(TaskGroupData {
|
|
||||||
members: TaskSet::new(),
|
|
||||||
descendants: TaskSet::new(),
|
|
||||||
}));
|
|
||||||
let a = if supervised {
|
|
||||||
let new_generation = incr_generation(&ancestors);
|
|
||||||
assert!(new_generation < uint::max_value);
|
|
||||||
// Child's ancestors start with the spawner.
|
|
||||||
// Build a new node in the ancestor list.
|
|
||||||
AncestorList(Some(Exclusive::new(AncestorNode {
|
|
||||||
generation: new_generation,
|
|
||||||
parent_group: spawner_group.tasks.clone(),
|
|
||||||
ancestors: ancestors,
|
|
||||||
})))
|
|
||||||
} else {
|
|
||||||
// Child has no ancestors.
|
|
||||||
AncestorList(None)
|
|
||||||
};
|
|
||||||
Some((g, a))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up membership in taskgroup and descendantship in all ancestor
|
|
||||||
// groups. If any enlistment fails, Some task was already failing, so
|
|
||||||
// don't let the child task run, and undo every successful enlistment.
|
|
||||||
fn enlist_many(child: &KillHandle, child_arc: &TaskGroupArc,
|
|
||||||
ancestors: &mut AncestorList) -> bool {
|
|
||||||
// Join this taskgroup.
|
|
||||||
let mut result = do access_group(child_arc) |child_tg| {
|
|
||||||
enlist_in_taskgroup(child_tg, child.clone(), true) // member
|
|
||||||
};
|
|
||||||
if result {
|
|
||||||
// Unwinding function in case any ancestral enlisting fails
|
|
||||||
let bail: |TaskGroupInner| = |tg| { leave_taskgroup(tg, child, false) };
|
|
||||||
// Attempt to join every ancestor group.
|
|
||||||
result = do each_ancestor(ancestors, bail) |ancestor_tg| {
|
|
||||||
// Enlist as a descendant, not as an actual member.
|
|
||||||
// Descendants don't kill ancestor groups on failure.
|
|
||||||
enlist_in_taskgroup(ancestor_tg, child.clone(), false)
|
|
||||||
};
|
|
||||||
// If any ancestor group fails, need to exit this group too.
|
|
||||||
if !result {
|
|
||||||
do access_group(child_arc) |child_tg| {
|
|
||||||
leave_taskgroup(child_tg, child, true); // member
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_raw(mut opts: TaskOpts, f: proc()) {
|
pub fn spawn_raw(mut opts: TaskOpts, f: proc()) {
|
||||||
assert!(in_green_task_context());
|
assert!(in_green_task_context());
|
||||||
|
|
||||||
let child_data = Cell::new(gen_child_taskgroup(opts.linked, opts.supervised));
|
|
||||||
let indestructible = opts.indestructible;
|
|
||||||
|
|
||||||
let child_wrapper: proc() = || {
|
|
||||||
// Child task runs this code.
|
|
||||||
|
|
||||||
// If child data is 'None', the enlist is vacuously successful.
|
|
||||||
let enlist_success = do child_data.take().map_default(true) |child_data| {
|
|
||||||
let child_data = Cell::new(child_data); // :(
|
|
||||||
do Local::borrow |me: &mut Task| {
|
|
||||||
let (child_tg, ancestors) = child_data.take();
|
|
||||||
let mut ancestors = ancestors;
|
|
||||||
let handle = me.death.kill_handle.get_ref();
|
|
||||||
// Atomically try to get into all of our taskgroups.
|
|
||||||
if enlist_many(handle, &child_tg, &mut ancestors) {
|
|
||||||
// Got in. We can run the provided child body, and can also run
|
|
||||||
// the taskgroup's exit-time-destructor afterward.
|
|
||||||
me.taskgroup = Some(Taskgroup(child_tg, ancestors, None));
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Should be run after the local-borrowed task is returned.
|
|
||||||
let f_cell = Cell::new(f);
|
|
||||||
if enlist_success {
|
|
||||||
if indestructible {
|
|
||||||
do unkillable { f_cell.take()() }
|
|
||||||
} else {
|
|
||||||
f_cell.take()()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut task = if opts.sched.mode != SingleThreaded {
|
let mut task = if opts.sched.mode != SingleThreaded {
|
||||||
if opts.watched {
|
if opts.watched {
|
||||||
Task::build_child(opts.stack_size, child_wrapper)
|
Task::build_child(opts.stack_size, f)
|
||||||
} else {
|
} else {
|
||||||
Task::build_root(opts.stack_size, child_wrapper)
|
Task::build_root(opts.stack_size, f)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -634,9 +127,9 @@ pub fn spawn_raw(mut opts: TaskOpts, f: proc()) {
|
|||||||
|
|
||||||
// Pin the new task to the new scheduler
|
// Pin the new task to the new scheduler
|
||||||
let new_task = if opts.watched {
|
let new_task = if opts.watched {
|
||||||
Task::build_homed_child(opts.stack_size, child_wrapper, Sched(new_sched_handle))
|
Task::build_homed_child(opts.stack_size, f, Sched(new_sched_handle))
|
||||||
} else {
|
} else {
|
||||||
Task::build_homed_root(opts.stack_size, child_wrapper, Sched(new_sched_handle))
|
Task::build_homed_root(opts.stack_size, f, Sched(new_sched_handle))
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a task that will later be used to join with the new scheduler
|
// Create a task that will later be used to join with the new scheduler
|
||||||
@ -711,7 +204,6 @@ fn test_spawn_raw_simple() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_spawn_raw_unsupervise() {
|
fn test_spawn_raw_unsupervise() {
|
||||||
let opts = task::TaskOpts {
|
let opts = task::TaskOpts {
|
||||||
linked: false,
|
|
||||||
watched: false,
|
watched: false,
|
||||||
notify_chan: None,
|
notify_chan: None,
|
||||||
.. default_task_opts()
|
.. default_task_opts()
|
||||||
@ -740,7 +232,6 @@ fn test_spawn_raw_notify_failure() {
|
|||||||
let (notify_po, notify_ch) = comm::stream();
|
let (notify_po, notify_ch) = comm::stream();
|
||||||
|
|
||||||
let opts = task::TaskOpts {
|
let opts = task::TaskOpts {
|
||||||
linked: false,
|
|
||||||
watched: false,
|
watched: false,
|
||||||
notify_chan: Some(notify_ch),
|
notify_chan: Some(notify_ch),
|
||||||
.. default_task_opts()
|
.. default_task_opts()
|
||||||
|
@ -25,7 +25,7 @@ do || {
|
|||||||
|
|
||||||
use ops::Drop;
|
use ops::Drop;
|
||||||
|
|
||||||
#[cfg(test)] use task::{failing, spawn};
|
#[cfg(test)] use task::failing;
|
||||||
|
|
||||||
pub trait Finally<T> {
|
pub trait Finally<T> {
|
||||||
fn finally(&self, dtor: ||) -> T;
|
fn finally(&self, dtor: ||) -> T;
|
||||||
|
@ -135,7 +135,6 @@ impl<T: Send> UnsafeArc<T> {
|
|||||||
/// block; otherwise, an unwrapping task can be killed by linked failure.
|
/// block; otherwise, an unwrapping task can be killed by linked failure.
|
||||||
pub fn unwrap(self) -> T {
|
pub fn unwrap(self) -> T {
|
||||||
let this = Cell::new(self); // argh
|
let this = Cell::new(self); // argh
|
||||||
do task::unkillable {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut this = this.take();
|
let mut this = this.take();
|
||||||
// The ~ dtor needs to run if this code succeeds.
|
// The ~ dtor needs to run if this code succeeds.
|
||||||
@ -166,7 +165,7 @@ impl<T: Send> UnsafeArc<T> {
|
|||||||
// taken either in the do block or in the finally block.
|
// taken either in the do block or in the finally block.
|
||||||
let c2_and_data = Cell::new((c2,data));
|
let c2_and_data = Cell::new((c2,data));
|
||||||
do (|| {
|
do (|| {
|
||||||
do task::rekillable { p1.take().recv(); }
|
p1.take().recv();
|
||||||
// Got here. Back in the 'unkillable' without getting killed.
|
// Got here. Back in the 'unkillable' without getting killed.
|
||||||
let (c2, data) = c2_and_data.take();
|
let (c2, data) = c2_and_data.take();
|
||||||
c2.send(true);
|
c2.send(true);
|
||||||
@ -196,7 +195,6 @@ impl<T: Send> UnsafeArc<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// As unwrap above, but without blocking. Returns 'UnsafeArcSelf(self)' if this is
|
/// As unwrap above, but without blocking. Returns 'UnsafeArcSelf(self)' if this is
|
||||||
/// not the last reference; 'UnsafeArcT(unwrapped_data)' if so.
|
/// not the last reference; 'UnsafeArcT(unwrapped_data)' if so.
|
||||||
@ -259,7 +257,6 @@ impl<T> Drop for UnsafeArc<T>{
|
|||||||
match data.unwrapper.take(Acquire) {
|
match data.unwrapper.take(Acquire) {
|
||||||
Some(~(message,response)) => {
|
Some(~(message,response)) => {
|
||||||
let cell = Cell::new((message, response, data));
|
let cell = Cell::new((message, response, data));
|
||||||
do task::unkillable {
|
|
||||||
let (message, response, data) = cell.take();
|
let (message, response, data) = cell.take();
|
||||||
// Send 'ready' and wait for a response.
|
// Send 'ready' and wait for a response.
|
||||||
message.send(());
|
message.send(());
|
||||||
@ -271,7 +268,6 @@ impl<T> Drop for UnsafeArc<T>{
|
|||||||
// Other task was killed. drop glue takes over.
|
// Other task was killed. drop glue takes over.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
None => {
|
None => {
|
||||||
// drop glue takes over.
|
// drop glue takes over.
|
||||||
}
|
}
|
||||||
@ -678,24 +674,4 @@ mod tests {
|
|||||||
assert!(x.unwrap() == ~~"hello");
|
assert!(x.unwrap() == ~~"hello");
|
||||||
assert!(res.recv().is_ok());
|
assert!(res.recv().is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn exclusive_new_unwrap_deadlock() {
|
|
||||||
// This is not guaranteed to get to the deadlock before being killed,
|
|
||||||
// but it will show up sometimes, and if the deadlock were not there,
|
|
||||||
// the test would nondeterministically fail.
|
|
||||||
let result = do task::try {
|
|
||||||
// a task that has two references to the same Exclusive::new will
|
|
||||||
// deadlock when it unwraps. nothing to be done about that.
|
|
||||||
let x = Exclusive::new(~~"hello");
|
|
||||||
let x2 = x.clone();
|
|
||||||
do task::spawn {
|
|
||||||
do 10.times { task::deschedule(); } // try to let the unwrapper go
|
|
||||||
fail!(); // punt it awake from its deadlock
|
|
||||||
}
|
|
||||||
let _z = x.unwrap();
|
|
||||||
unsafe { do x2.with |_hello| { } }
|
|
||||||
};
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -179,9 +179,9 @@ fn main() {
|
|||||||
|
|
||||||
let (from_parent, to_child) = comm::stream();
|
let (from_parent, to_child) = comm::stream();
|
||||||
|
|
||||||
do task::spawn_with(from_parent) |from_parent| {
|
do spawn {
|
||||||
make_sequence_processor(sz, &from_parent, &to_parent_);
|
make_sequence_processor(sz, &from_parent, &to_parent_);
|
||||||
};
|
}
|
||||||
|
|
||||||
to_child
|
to_child
|
||||||
}.collect::<~[Chan<~[u8]>]>();
|
}.collect::<~[Chan<~[u8]>]>();
|
||||||
|
@ -28,7 +28,7 @@ fn child_generation(gens_left: uint, c: comm::Chan<()>) {
|
|||||||
// With this code, only as many generations are alive at a time as tasks
|
// With this code, only as many generations are alive at a time as tasks
|
||||||
// alive at a time,
|
// alive at a time,
|
||||||
let c = Cell::new(c);
|
let c = Cell::new(c);
|
||||||
do task::spawn_supervised {
|
do spawn {
|
||||||
let c = c.take();
|
let c = c.take();
|
||||||
if gens_left & 1 == 1 {
|
if gens_left & 1 == 1 {
|
||||||
task::deschedule(); // shake things up a bit
|
task::deschedule(); // shake things up a bit
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// xfail-pretty
|
// xfail-pretty
|
||||||
|
// xfail-test linked failure
|
||||||
|
|
||||||
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
|
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
|
||||||
// file at the top-level directory of this distribution and at
|
// file at the top-level directory of this distribution and at
|
||||||
@ -35,8 +36,6 @@ fn grandchild_group(num_tasks: uint) {
|
|||||||
for _ in range(0, num_tasks) {
|
for _ in range(0, num_tasks) {
|
||||||
let ch = ch.clone();
|
let ch = ch.clone();
|
||||||
let mut t = task::task();
|
let mut t = task::task();
|
||||||
t.linked();
|
|
||||||
t.unwatched();
|
|
||||||
do t.spawn { // linked
|
do t.spawn { // linked
|
||||||
ch.send(());
|
ch.send(());
|
||||||
let (p, _c) = stream::<()>();
|
let (p, _c) = stream::<()>();
|
||||||
|
@ -10,8 +10,11 @@
|
|||||||
|
|
||||||
// error-pattern:task '<unnamed>' failed at 'test'
|
// error-pattern:task '<unnamed>' failed at 'test'
|
||||||
|
|
||||||
|
use std::task;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
do spawn {
|
do task::try {
|
||||||
fail!("test");
|
fail!("test");
|
||||||
}
|
1
|
||||||
|
}.unwrap()
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,13 @@
|
|||||||
|
|
||||||
// error-pattern:task 'owned name' failed at 'test'
|
// error-pattern:task 'owned name' failed at 'test'
|
||||||
|
|
||||||
|
use std::task;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut t = ::std::task::task();
|
let mut t = task::task();
|
||||||
t.name(~"owned name");
|
t.name(~"owned name");
|
||||||
do t.spawn {
|
do t.try {
|
||||||
fail!("test");
|
fail!("test");
|
||||||
}
|
1
|
||||||
|
}.unwrap()
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,8 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
let mut t = ::std::task::task();
|
let mut t = ::std::task::task();
|
||||||
t.name("send name".to_send_str());
|
t.name("send name".to_send_str());
|
||||||
do t.spawn {
|
do t.try {
|
||||||
fail!("test");
|
fail!("test");
|
||||||
}
|
3
|
||||||
|
}.unwrap()
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
let mut t = ::std::task::task();
|
let mut t = ::std::task::task();
|
||||||
t.name("static name");
|
t.name("static name");
|
||||||
do t.spawn {
|
do t.try {
|
||||||
fail!("test");
|
fail!("test");
|
||||||
}
|
}.unwrap()
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ use std::task;
|
|||||||
fn main() {
|
fn main() {
|
||||||
// the purpose of this test is to make sure that task::spawn()
|
// the purpose of this test is to make sure that task::spawn()
|
||||||
// works when provided with a bare function:
|
// works when provided with a bare function:
|
||||||
task::spawn(startfn);
|
task::try(startfn).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn startfn() {
|
fn startfn() {
|
||||||
|
@ -18,5 +18,5 @@ fn f() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
task::spawn_unlinked(f);
|
task::spawn(f);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ fn f(c: SharedChan<bool>) {
|
|||||||
pub fn main() {
|
pub fn main() {
|
||||||
let (p, c) = stream();
|
let (p, c) = stream();
|
||||||
let c = SharedChan::new(c);
|
let c = SharedChan::new(c);
|
||||||
task::spawn_unlinked(|| f(c.clone()) );
|
task::spawn(|| f(c.clone()) );
|
||||||
error!("hiiiiiiiii");
|
error!("hiiiiiiiii");
|
||||||
assert!(p.recv());
|
assert!(p.recv());
|
||||||
}
|
}
|
||||||
|
@ -35,5 +35,5 @@ fn f() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
task::spawn_unlinked(f);
|
task::spawn(f);
|
||||||
}
|
}
|
||||||
|
@ -18,5 +18,5 @@ fn f() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
task::spawn_unlinked(f);
|
task::spawn(f);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user