// Copyright 2012 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. /*! * Task management. * * An executing Rust program consists of a tree of tasks, each with their own * stack, and sole ownership of their allocated heap data. Tasks communicate * with each other using ports and channels. * * When a task fails, that failure will propagate to its parent (the task * that spawned it) and the parent will fail as well. The reverse is not * true: when a parent task fails its children will continue executing. When * the root (main) task fails, all tasks fail, and then so does the entire * process. * * Tasks may execute in parallel and are scheduled automatically by the * runtime. * * # Example * * ~~~ * do spawn { * log(error, "Hello, World!"); * } * ~~~ */ use cell::Cell; use cmp::Eq; use result::Result; use comm::{stream, Chan, GenericChan, GenericPort, Port}; use prelude::*; use result; use task::rt::{task_id, sched_id, rust_task}; use util; use util::replace; #[cfg(test)] use comm::SharedChan; mod local_data_priv; pub mod local_data; pub mod rt; pub mod spawn; /// A handle to a scheduler #[deriving(Eq)] pub enum Scheduler { SchedulerHandle(sched_id) } /// A handle to a task #[deriving(Eq)] pub enum Task { TaskHandle(task_id) } /** * Indicates the manner in which a task exited. * * A task that completes without failing is considered to exit successfully. * Supervised ancestors and linked siblings may yet fail after this task * succeeds. Also note that in such a case, it may be nondeterministic whether * linked failure or successful exit happen first. * * If you wish for this result's delivery to block until all linked and/or * children tasks complete, recommend using a result future. */ #[deriving(Eq)] pub enum TaskResult { Success, Failure, } /// Scheduler modes #[deriving(Eq)] pub enum SchedMode { /// Run task on the default scheduler DefaultScheduler, /// Run task on the current scheduler CurrentScheduler, /// Run task on a specific scheduler ExistingScheduler(Scheduler), /** * Tasks are scheduled on the main OS thread * * The main OS thread is the thread used to launch the runtime which, * in most cases, is the process's initial thread as created by the OS. */ PlatformThread, /// All tasks run in the same OS thread SingleThreaded, /// Tasks are distributed among available CPUs ThreadPerCore, /// Each task runs in its own OS thread ThreadPerTask, /// Tasks are distributed among a fixed number of OS threads ManualThreads(uint), } /** * Scheduler configuration options * * # Fields * * * sched_mode - The operating mode of the scheduler * * * foreign_stack_size - The size of the foreign stack, in bytes * * Rust code runs on Rust-specific stacks. When Rust code calls foreign * code (via functions in foreign modules) it switches to a typical, large * stack appropriate for running code written in languages like C. By * default these foreign stacks have unspecified size, but with this * option their size can be precisely specified. */ pub struct SchedOpts { mode: SchedMode, foreign_stack_size: Option, } /** * Task configuration options * * # 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. * * * notify_chan - Enable lifecycle notifications on the given channel * * * sched - Specify the configuration of a new scheduler to create the task * in * * By default, every task is created in the same scheduler as its * parent, where it is scheduled cooperatively with all other tasks * in that scheduler. Some specialized applications may want more * control over their scheduling, in which case they can be spawned * into a new scheduler with the specific properties required. * * This is of particular importance for libraries which want to call * into foreign code that blocks. Without doing so in a different * scheduler other tasks will be impeded or even blocked indefinitely. */ pub struct TaskOpts { linked: bool, supervised: bool, mut notify_chan: Option>, sched: SchedOpts } /** * The task builder type. * * Provides detailed control over the properties and behavior of new tasks. */ // NB: Builders are designed to be single-use because they do stateful // things that get weird when reusing - e.g. if you create a result future // it only applies to a single task, so then you have to maintain Some // potentially tricky state to ensure that everything behaves correctly // 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, mut gen_body: Option<~fn(v: ~fn()) -> ~fn()>, can_not_copy: Option, mut consumed: bool, } /** * Generate the base configuration for spawning a task, off of which more * configuration methods can be chained. * For example, task().unlinked().spawn is equivalent to spawn_unlinked. */ pub fn task() -> TaskBuilder { TaskBuilder { opts: default_task_opts(), gen_body: None, can_not_copy: None, mut consumed: false, } } #[doc(hidden)] // FIXME #3538 priv impl TaskBuilder { fn consume(&self) -> TaskBuilder { if self.consumed { fail!(~"Cannot copy a task_builder"); // Fake move mode on self } self.consumed = true; let gen_body = replace(&mut self.gen_body, None); let notify_chan = replace(&mut self.opts.notify_chan, None); TaskBuilder { opts: TaskOpts { linked: self.opts.linked, supervised: self.opts.supervised, notify_chan: notify_chan, sched: self.opts.sched }, gen_body: gen_body, can_not_copy: None, consumed: false } } } pub impl TaskBuilder { /** * Decouple the child task's failure from the parent's. If either fails, * the other will not be killed. */ fn unlinked(&self) -> TaskBuilder { let notify_chan = replace(&mut self.opts.notify_chan, None); TaskBuilder { opts: TaskOpts { linked: false, supervised: self.opts.supervised, notify_chan: notify_chan, sched: self.opts.sched }, can_not_copy: None, .. self.consume() } } /** * 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. */ fn supervised(&self) -> TaskBuilder { let notify_chan = replace(&mut self.opts.notify_chan, None); TaskBuilder { opts: TaskOpts { linked: false, supervised: true, notify_chan: notify_chan, sched: self.opts.sched }, can_not_copy: None, .. self.consume() } } /** * Link the child task's and parent task's failures. If either fails, the * other will be killed. */ fn linked(&self) -> TaskBuilder { let notify_chan = replace(&mut self.opts.notify_chan, None); TaskBuilder { opts: TaskOpts { linked: true, supervised: false, notify_chan: notify_chan, sched: self.opts.sched }, can_not_copy: None, .. self.consume() } } /** * Get a future representing the exit status of the task. * * Taking the value of the future will block until the child task * terminates. The future-receiving callback specified will be called * *before* the task is spawned; as such, do not invoke .get() within the * closure; rather, store it in an outer variable/list for later use. * * Note that the future returning by this function is only useful for * obtaining the value of the next task to be spawning with the * builder. If additional tasks are spawned with the same builder * then a new result future must be obtained prior to spawning each * task. * * # Failure * Fails if a future_result was already set for this task. */ fn future_result(&self, blk: &fn(v: Port)) -> TaskBuilder { // FIXME (#3725): Once linked failure and notification are // handled in the library, I can imagine implementing this by just // registering an arbitrary number of task::on_exit handlers and // sending out messages. if self.opts.notify_chan.is_some() { fail!(~"Can't set multiple future_results for one task!"); } // Construct the future and give it to the caller. let (notify_pipe_po, notify_pipe_ch) = stream::(); blk(notify_pipe_po); // Reconfigure self to use a notify channel. TaskBuilder { opts: TaskOpts { linked: self.opts.linked, supervised: self.opts.supervised, notify_chan: Some(notify_pipe_ch), sched: self.opts.sched }, can_not_copy: None, .. self.consume() } } /// Configure a custom scheduler mode for the task. fn sched_mode(&self, mode: SchedMode) -> TaskBuilder { let notify_chan = replace(&mut self.opts.notify_chan, None); TaskBuilder { opts: TaskOpts { linked: self.opts.linked, supervised: self.opts.supervised, notify_chan: notify_chan, sched: SchedOpts { mode: mode, foreign_stack_size: None} }, can_not_copy: None, .. self.consume() } } /** * Add a wrapper to the body of the spawned task. * * Before the task is spawned it is passed through a 'body generator' * function that may perform local setup operations as well as wrap * the task body in remote setup operations. With this the behavior * of tasks can be extended in simple ways. * * This function augments the current body generator with a new body * generator by applying the task body which results from the * existing body generator to the new body generator. */ fn add_wrapper(&self, wrapper: ~fn(v: ~fn()) -> ~fn()) -> TaskBuilder { let prev_gen_body = replace(&mut self.gen_body, None); let prev_gen_body = match prev_gen_body { Some(gen) => gen, None => { let f: ~fn(~fn()) -> ~fn() = |body| body; f } }; let prev_gen_body = Cell(prev_gen_body); let next_gen_body = { let f: ~fn(~fn()) -> ~fn() = |body| { let prev_gen_body = prev_gen_body.take(); wrapper(prev_gen_body(body)) }; f }; let notify_chan = replace(&mut self.opts.notify_chan, None); TaskBuilder { opts: TaskOpts { linked: self.opts.linked, supervised: self.opts.supervised, notify_chan: notify_chan, sched: self.opts.sched }, gen_body: Some(next_gen_body), can_not_copy: None, .. self.consume() } } /** * Creates and executes a new child task * * Sets up a new task with its own call stack and schedules it to run * the provided unique closure. The task has the properties and behavior * specified by the task_builder. * * # Failure * * When spawning into a new scheduler, the number of threads requested * must be greater than zero. */ fn spawn(&self, f: ~fn()) { let gen_body = replace(&mut self.gen_body, None); let notify_chan = replace(&mut self.opts.notify_chan, None); let x = self.consume(); let opts = TaskOpts { linked: x.opts.linked, supervised: x.opts.supervised, notify_chan: notify_chan, sched: x.opts.sched }; let f = match gen_body { Some(gen) => { gen(f) } None => { f } }; spawn::spawn_raw(opts, f); } /// Runs a task, while transfering ownership of one argument to the child. fn spawn_with(&self, arg: A, f: ~fn(v: A)) { let arg = Cell(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. * * # Return value * * If the function executed successfully then try returns result::ok * containing the value returned by the function. If the function fails * then try returns result::err containing nil. * * # Failure * Fails if a future_result was already set for this task. */ fn try(&self, f: ~fn() -> T) -> Result { let (po, ch) = stream::(); let mut result = None; let fr_task_builder = self.future_result(|+r| { result = Some(r); }); do fr_task_builder.spawn || { ch.send(f()); } match result.unwrap().recv() { Success => result::Ok(po.recv()), Failure => result::Err(()) } } } /* Task construction */ pub fn default_task_opts() -> TaskOpts { /*! * The default task options * * By default all tasks are supervised by their parent, are spawned * into the same scheduler, and do not post lifecycle notifications. */ TaskOpts { linked: true, supervised: false, notify_chan: None, sched: SchedOpts { mode: DefaultScheduler, foreign_stack_size: None } } } /* Spawn convenience functions */ pub fn spawn(f: ~fn()) { /*! * Creates and executes a new child task * * Sets up a new task with its own call stack and schedules it to run * the provided unique closure. * * This function is equivalent to `task().spawn(f)`. */ task().spawn(f) } pub fn spawn_unlinked(f: ~fn()) { /*! * Creates a child task unlinked from the current one. If either this * task or the child task fails, the other will not be killed. */ task().unlinked().spawn(f) } pub fn spawn_supervised(f: ~fn()) { /*! * 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. */ task().supervised().spawn(f) } pub fn spawn_with(arg: A, f: ~fn(v: A)) { /*! * Runs a task, while transfering ownership of one argument to the * child. * * This is useful for transfering ownership of noncopyables to * another task. * * This function is equivalent to `task().spawn_with(arg, f)`. */ task().spawn_with(arg, f) } pub fn spawn_sched(mode: SchedMode, f: ~fn()) { /*! * Creates a new task on a new or existing scheduler * When there are no more tasks to execute the * scheduler terminates. * * # Failure * * In manual threads mode the number of threads requested must be * greater than zero. */ task().sched_mode(mode).spawn(f) } pub fn try(f: ~fn() -> T) -> Result { /*! * Execute a function in another task and return either the return value * of the function or result::err. * * This is equivalent to task().supervised().try. */ task().supervised().try(f) } /* Lifecycle functions */ pub fn yield() { //! Yield control to the task scheduler unsafe { let task_ = rt::rust_get_task(); let killed = rt::rust_task_yield(task_); if killed && !failing() { fail!(~"killed"); } } } pub fn failing() -> bool { //! True if the running task has failed unsafe { rt::rust_task_is_unwinding(rt::rust_get_task()) } } pub fn get_task() -> Task { //! Get a handle to the running task unsafe { TaskHandle(rt::get_task_id()) } } pub fn get_scheduler() -> Scheduler { SchedulerHandle(unsafe { rt::rust_get_sched_id() }) } /** * Temporarily make the task unkillable * * # Example * * ~~~ * do task::unkillable { * // detach / yield / destroy must all be called together * rustrt::rust_port_detach(po); * // This must not result in the current task being killed * task::yield(); * rustrt::rust_port_destroy(po); * } * ~~~ */ pub unsafe fn unkillable(f: &fn() -> U) -> U { struct AllowFailure { t: *rust_task, drop { unsafe { rt::rust_task_allow_kill(self.t); } } } fn AllowFailure(t: *rust_task) -> AllowFailure{ AllowFailure { t: t } } let t = rt::rust_get_task(); let _allow_failure = AllowFailure(t); rt::rust_task_inhibit_kill(t); f() } /// The inverse of unkillable. Only ever to be used nested in unkillable(). pub unsafe fn rekillable(f: &fn() -> U) -> U { struct DisallowFailure { t: *rust_task, drop { unsafe { rt::rust_task_inhibit_kill(self.t); } } } fn DisallowFailure(t: *rust_task) -> DisallowFailure { DisallowFailure { t: t } } let t = rt::rust_get_task(); let _allow_failure = DisallowFailure(t); rt::rust_task_allow_kill(t); f() } /** * A stronger version of unkillable that also inhibits scheduling operations. * For use with exclusive ARCs, which use pthread mutexes directly. */ pub unsafe fn atomically(f: &fn() -> U) -> U { struct DeferInterrupts { t: *rust_task, drop { unsafe { rt::rust_task_allow_yield(self.t); rt::rust_task_allow_kill(self.t); } } } fn DeferInterrupts(t: *rust_task) -> DeferInterrupts { DeferInterrupts { t: t } } let t = rt::rust_get_task(); let _interrupts = DeferInterrupts(t); rt::rust_task_inhibit_kill(t); rt::rust_task_inhibit_yield(t); f() } #[test] #[should_fail] #[ignore(cfg(windows))] fn test_cant_dup_task_builder() { let b = task().unlinked(); do b.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 b.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. // !!! These tests are dangerous. If Something is buggy, they will hang, !!! // !!! instead of exiting cleanly. This might wedge the buildbots. !!! #[test] #[ignore(cfg(windows))] fn test_spawn_unlinked_unsup_no_fail_down() { // grandchild sends on a port 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. for iter::repeat(16) { task::yield(); } ch.send(()); // If killed first, grandparent hangs. } fail!(); // Shouldn't kill either (grand)parent or (grand)child. } po.recv(); } #[test] #[ignore(cfg(windows))] fn test_spawn_unlinked_unsup_no_fail_up() { // child unlinked fails do spawn_unlinked { fail!(); } } #[test] #[ignore(cfg(windows))] fn test_spawn_unlinked_sup_no_fail_up() { // child unlinked fails do spawn_supervised { fail!(); } // Give child a chance to fail-but-not-kill-us. for iter::repeat(16) { task::yield(); } } #[test] #[should_fail] #[ignore(cfg(windows))] fn test_spawn_unlinked_sup_fail_down() { do spawn_supervised { loop { task::yield(); } } fail!(); // Shouldn't leave a child hanging around. } #[test] #[should_fail] #[ignore(cfg(windows))] fn test_spawn_linked_sup_fail_up() { // child fails; parent fails let (po, _ch) = stream::<()>(); // 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 opts = { let mut opts = default_task_opts(); opts.linked = true; opts.supervised = true; opts }; let b0 = task(); let b1 = TaskBuilder { opts: opts, can_not_copy: None, .. b0 }; do b1.spawn { fail!(); } po.recv(); // We should get punted awake } #[test] #[should_fail] #[ignore(cfg(windows))] fn test_spawn_linked_sup_fail_down() { // parent fails; child fails // We have to cheat with opts - the interface doesn't support them because // they don't make sense (redundant with task().supervised()). let opts = { let mut opts = default_task_opts(); opts.linked = true; opts.supervised = true; opts }; let b0 = task(); let b1 = TaskBuilder { opts: opts, can_not_copy: None, .. b0 }; do b1.spawn { loop { task::yield(); } } fail!(); // *both* mechanisms would be wrong if this didn't kill the child } #[test] #[should_fail] #[ignore(cfg(windows))] fn test_spawn_linked_unsup_fail_up() { // child fails; parent fails let (po, _ch) = stream::<()>(); // Default options are to spawn linked & unsupervised. do spawn { fail!(); } po.recv(); // We should get punted awake } #[test] #[should_fail] #[ignore(cfg(windows))] fn test_spawn_linked_unsup_fail_down() { // parent fails; child fails // Default options are to spawn linked & unsupervised. do spawn { loop { task::yield(); } } fail!(); } #[test] #[should_fail] #[ignore(cfg(windows))] fn test_spawn_linked_unsup_default_opts() { // parent fails; child fails // Make sure the above test is the same as this one. do task().linked().spawn { loop { task::yield(); } } fail!(); } // A couple bonus linked failure tests - testing for failure propagation even // when the middle task exits successfully early before kill signals are sent. #[test] #[should_fail] #[ignore(cfg(windows))] fn test_spawn_failure_propagate_grandchild() { // Middle task exits; does grandparent's failure propagate across the gap? do spawn_supervised { do spawn_supervised { loop { task::yield(); } } } for iter::repeat(16) { task::yield(); } fail!(); } #[test] #[should_fail] #[ignore(cfg(windows))] fn test_spawn_failure_propagate_secondborn() { // First-born child exits; does parent's failure propagate to sibling? do spawn_supervised { do spawn { // linked loop { task::yield(); } } } for iter::repeat(16) { task::yield(); } fail!(); } #[test] #[should_fail] #[ignore(cfg(windows))] fn test_spawn_failure_propagate_nephew_or_niece() { // Our sibling exits; does our failure propagate to sibling's child? do spawn { // linked do spawn_supervised { loop { task::yield(); } } } for iter::repeat(16) { task::yield(); } fail!(); } #[test] #[should_fail] #[ignore(cfg(windows))] fn test_spawn_linked_sup_propagate_sibling() { // Middle sibling exits - does eldest's failure propagate to youngest? do spawn { // linked do spawn { // linked loop { task::yield(); } } } for iter::repeat(16) { task::yield(); } fail!(); } #[test] fn test_run_basic() { let (po, ch) = stream::<()>(); do task().spawn { ch.send(()); } po.recv(); } #[test] struct Wrapper { mut f: Option> } #[test] fn test_add_wrapper() { let (po, ch) = stream::<()>(); let b0 = task(); let ch = Wrapper { f: Some(ch) }; let b1 = do b0.add_wrapper |body| { let ch = Wrapper { f: Some(ch.f.swap_unwrap()) }; let result: ~fn() = || { let ch = ch.f.swap_unwrap(); body(); ch.send(()); }; result }; do b1.spawn { } po.recv(); } #[test] #[ignore(cfg(windows))] fn test_future_result() { let mut result = None; do task().future_result(|+r| { result = Some(r); }).spawn { } assert!(result.unwrap().recv() == Success); result = None; do task().future_result(|+r| { result = Some(r); }).unlinked().spawn { fail!(); } assert!(result.unwrap().recv() == Failure); } #[test] #[should_fail] #[ignore(cfg(windows))] fn test_back_to_the_future_result() { let _ = task().future_result(util::ignore).future_result(util::ignore); } #[test] fn test_try_success() { match do try { ~"Success!" } { result::Ok(~"Success!") => (), _ => fail!() } } #[test] #[ignore(cfg(windows))] fn test_try_fail() { match do try { fail!() } { result::Err(()) => (), result::Ok(()) => fail!() } } #[test] #[should_fail] #[ignore(cfg(windows))] fn test_spawn_sched_no_threads() { do spawn_sched(ManualThreads(0u)) { } } #[test] fn test_spawn_sched() { let (po, ch) = stream::<()>(); let ch = SharedChan::new(ch); fn f(i: int, ch: SharedChan<()>) { let parent_sched_id = unsafe { rt::rust_get_sched_id() }; do spawn_sched(SingleThreaded) { let child_sched_id = unsafe { rt::rust_get_sched_id() }; assert!(parent_sched_id != child_sched_id); if (i == 0) { ch.send(()); } else { f(i - 1, ch.clone()); } }; } f(10, ch); po.recv(); } #[test] fn test_spawn_sched_childs_on_default_sched() { let (po, ch) = stream(); // Assuming tests run on the default scheduler let default_id = unsafe { rt::rust_get_sched_id() }; let ch = Wrapper { f: Some(ch) }; do spawn_sched(SingleThreaded) { let parent_sched_id = unsafe { rt::rust_get_sched_id() }; let ch = Wrapper { f: Some(ch.f.swap_unwrap()) }; do spawn { let ch = ch.f.swap_unwrap(); let child_sched_id = unsafe { rt::rust_get_sched_id() }; assert!(parent_sched_id != child_sched_id); assert!(child_sched_id == default_id); ch.send(()); }; }; po.recv(); } #[cfg(test)] mod testrt { use libc; #[nolink] pub extern { unsafe fn rust_dbg_lock_create() -> *libc::c_void; unsafe fn rust_dbg_lock_destroy(lock: *libc::c_void); unsafe fn rust_dbg_lock_lock(lock: *libc::c_void); unsafe fn rust_dbg_lock_unlock(lock: *libc::c_void); unsafe fn rust_dbg_lock_wait(lock: *libc::c_void); unsafe fn rust_dbg_lock_signal(lock: *libc::c_void); } } #[test] fn test_spawn_sched_blocking() { unsafe { // Testing that a task in one scheduler can block in foreign code // without affecting other schedulers for iter::repeat(20u) { let (start_po, start_ch) = stream(); let (fin_po, fin_ch) = stream(); let lock = testrt::rust_dbg_lock_create(); do spawn_sched(SingleThreaded) { unsafe { testrt::rust_dbg_lock_lock(lock); start_ch.send(()); // Block the scheduler thread testrt::rust_dbg_lock_wait(lock); testrt::rust_dbg_lock_unlock(lock); fin_ch.send(()); } }; // Wait until the other task has its lock start_po.recv(); fn pingpong(po: &Port, ch: &Chan) { let mut val = 20; while val > 0 { val = po.recv(); ch.send(val - 1); } } let (setup_po, setup_ch) = stream(); let (parent_po, parent_ch) = stream(); do spawn { let (child_po, child_ch) = stream(); setup_ch.send(child_ch); pingpong(&child_po, &parent_ch); }; let child_ch = setup_po.recv(); child_ch.send(20); pingpong(&parent_po, &child_ch); testrt::rust_dbg_lock_lock(lock); testrt::rust_dbg_lock_signal(lock); testrt::rust_dbg_lock_unlock(lock); fin_po.recv(); testrt::rust_dbg_lock_destroy(lock); } } } #[cfg(test)] fn avoid_copying_the_body(spawnfn: &fn(v: ~fn())) { let (p, ch) = stream::(); let x = ~1; let x_in_parent = ptr::addr_of(&(*x)) as uint; do spawnfn || { let x_in_child = ptr::addr_of(&(*x)) as uint; ch.send(x_in_child); } let x_in_child = p.recv(); assert!(x_in_parent == x_in_child); } #[test] fn test_avoid_copying_the_body_spawn() { avoid_copying_the_body(spawn); } #[test] fn test_avoid_copying_the_body_task_spawn() { do avoid_copying_the_body |f| { do task().spawn || { f(); } } } #[test] fn test_avoid_copying_the_body_try() { do avoid_copying_the_body |f| { do try || { f() }; } } #[test] fn test_avoid_copying_the_body_unlinked() { do avoid_copying_the_body |f| { do spawn_unlinked || { f(); } } } #[test] fn test_platform_thread() { let (po, ch) = stream(); do task().sched_mode(PlatformThread).spawn { ch.send(()); } po.recv(); } #[test] #[ignore(cfg(windows))] #[should_fail] fn test_unkillable() { let (po, ch) = stream(); // We want to do this after failing do spawn_unlinked { for iter::repeat(10) { yield() } ch.send(()); } do spawn { yield(); // 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(); } #[test] #[ignore(cfg(windows))] #[should_fail] fn test_unkillable_nested() { let (po, ch) = comm::stream(); // We want to do this after failing do spawn_unlinked || { for iter::repeat(10) { yield() } ch.send(()); } do spawn { yield(); // 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] #[should_fail] #[ignore(cfg(windows))] fn test_atomically() { unsafe { do atomically { yield(); } } } #[test] fn test_atomically2() { unsafe { do atomically { } } yield(); // shouldn't fail } #[test] #[should_fail] #[ignore(cfg(windows))] fn test_atomically_nested() { unsafe { do atomically { do atomically { } yield(); } } } #[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) // (well, it would if the constant were 8000+ - I lowered it to be more // valgrind-friendly. try this at home, instead..!) static generations: uint = 16; fn child_no(x: uint) -> ~fn() { return || { if x < generations { task::spawn(child_no(x+1)); } } } task::spawn(child_no(0)); } #[test] fn test_sched_thread_per_core() { let (port, chan) = comm::stream(); do spawn_sched(ThreadPerCore) || { unsafe { let cores = rt::rust_num_threads(); let reported_threads = rt::rust_sched_threads(); assert!((cores as uint == reported_threads as uint)); chan.send(()); } } port.recv(); } #[test] fn test_spawn_thread_on_demand() { let (port, chan) = comm::stream(); do spawn_sched(ManualThreads(2)) || { unsafe { let max_threads = rt::rust_sched_threads(); assert!((max_threads as int == 2)); let running_threads = rt::rust_sched_current_nonlazy_threads(); assert!((running_threads as int == 1)); let (port2, chan2) = comm::stream(); do spawn_sched(CurrentScheduler) || { chan2.send(()); } let running_threads2 = rt::rust_sched_current_nonlazy_threads(); assert!((running_threads2 as int == 2)); port2.recv(); chan.send(()); } } port.recv(); } #[test] fn test_simple_newsched_spawn() { use rt::test::run_in_newsched_task; do run_in_newsched_task { spawn(||()) } }