diff options
Diffstat (limited to 'src/libstd/task/mod.rs')
| -rw-r--r-- | src/libstd/task/mod.rs | 649 |
1 files changed, 7 insertions, 642 deletions
diff --git a/src/libstd/task/mod.rs b/src/libstd/task/mod.rs index f9b918d6d12..485fe9edf0e 100644 --- a/src/libstd/task/mod.rs +++ b/src/libstd/task/mod.rs @@ -62,16 +62,12 @@ use rt::in_green_task_context; use rt::local::Local; use rt::task::{UnwindResult, Success, Failure}; use send_str::{SendStr, IntoSendStr}; -use unstable::finally::Finally; use util; #[cfg(test)] use any::Any; -#[cfg(test)] use cast; #[cfg(test)] use comm::SharedChan; -#[cfg(test)] use comm; #[cfg(test)] use ptr; #[cfg(test)] use result; -#[cfg(test)] use task; pub mod spawn; @@ -86,8 +82,6 @@ pub mod spawn; /// children tasks complete, recommend using a result future. pub type TaskResult = Result<(), ~Any>; -pub struct LinkedFailure; - pub struct TaskResultPort { priv port: Port<UnwindResult> } @@ -141,24 +135,11 @@ pub struct SchedOpts { * * # 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 * before reporting its own exit status. (This delays the parent * task's death and cleanup until after all transitively watched * 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 * * * 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. */ pub struct TaskOpts { - priv linked: bool, - priv supervised: bool, priv watched: bool, - priv indestructible: bool, priv notify_chan: Option<Chan<UnwindResult>>, name: Option<SendStr>, sched: SchedOpts, @@ -191,13 +169,10 @@ pub struct TaskOpts { // 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 // the run function move them in. - -// FIXME (#3724): Replace the 'consumed' bit with move mode on self pub struct TaskBuilder { opts: TaskOpts, priv gen_body: Option<proc(v: proc()) -> proc()>, priv can_not_copy: Option<util::NonCopyable>, - priv consumed: bool, } /** @@ -210,25 +185,17 @@ pub fn task() -> TaskBuilder { opts: default_task_opts(), gen_body: None, can_not_copy: None, - consumed: false, } } impl TaskBuilder { - fn consume(&mut self) -> TaskBuilder { - if self.consumed { - fail!("Cannot copy a task_builder"); // Fake move mode on self - } - self.consumed = true; + fn consume(mut self) -> TaskBuilder { let gen_body = self.gen_body.take(); let notify_chan = self.opts.notify_chan.take(); let name = self.opts.name.take(); TaskBuilder { opts: TaskOpts { - linked: self.opts.linked, - supervised: self.opts.supervised, watched: self.opts.watched, - indestructible: self.opts.indestructible, notify_chan: notify_chan, name: name, sched: self.opts.sched, @@ -236,34 +203,9 @@ impl TaskBuilder { }, gen_body: gen_body, 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 /// all transitively-watched grandchildren) before reporting its own. pub fn watched(&mut self) { @@ -276,13 +218,6 @@ impl TaskBuilder { 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. /// /// 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 * 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 notify_chan = self.opts.notify_chan.take(); let name = self.opts.name.take(); let x = self.consume(); let opts = TaskOpts { - linked: x.opts.linked, - supervised: x.opts.supervised, watched: x.opts.watched, - indestructible: x.opts.indestructible, notify_chan: notify_chan, name: name, sched: x.opts.sched, @@ -398,14 +330,6 @@ impl TaskBuilder { 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 * of the function or result::err. @@ -419,7 +343,7 @@ impl TaskBuilder { * # Failure * 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 result = self.future_result(); @@ -447,10 +371,7 @@ pub fn default_task_opts() -> TaskOpts { */ TaskOpts { - linked: true, - supervised: false, watched: true, - indestructible: false, notify_chan: None, name: None, sched: SchedOpts { @@ -469,56 +390,10 @@ pub fn default_task_opts() -> TaskOpts { /// /// This function is equivalent to `task().spawn(f)`. pub fn spawn(f: proc()) { - let mut task = task(); + let task = task(); 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()) { /*! * 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. */ - let mut task = task(); - task.supervised(); + let task = task(); 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: // {un,}linked {un,}supervised failure propagation {up,down}wards. @@ -752,207 +473,6 @@ fn test_cant_dup_task_builder() { #[cfg(test)] 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] fn test_unnamed_task() { use rt::test::run_in_uv_task; @@ -1014,7 +534,7 @@ fn test_send_named_task() { #[test] fn test_run_basic() { let (po, ch) = stream::<()>(); - let mut builder = task(); + let builder = task(); do builder.spawn { ch.send(()); } @@ -1053,7 +573,6 @@ fn test_future_result() { let mut builder = task(); let result = builder.future_result(); - builder.unlinked(); do builder.spawn { fail!(); } @@ -1224,7 +743,7 @@ fn test_avoid_copying_the_body_spawn() { #[test] fn test_avoid_copying_the_body_task_spawn() { do avoid_copying_the_body |f| { - let mut builder = task(); + let builder = task(); do builder.spawn || { f(); } @@ -1241,86 +760,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] fn test_child_doesnt_ref_parent() { // If the child refcounts the parent task, this will stack overflow when // climbing the task tree to dereference each ancestor. (See #1789) @@ -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] fn test_try_fail_message_static_str() { 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] fn test_try_fail_message_unit_struct() { struct Juju; |
