// Copyright 2012-2013 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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!");
 * }
 * ~~~
 */

#[allow(missing_doc)];

use prelude::*;

use cell::Cell;
use comm::{stream, Chan, GenericChan, GenericPort, Port};
use result::Result;
use result;
use rt::in_green_task_context;
use rt::local::Local;
use unstable::finally::Finally;
use util;

#[cfg(test)] use cast;
#[cfg(test)] use comm::SharedChan;
#[cfg(test)] use comm;
#[cfg(test)] use ptr;
#[cfg(test)] use task;

mod local_data_priv;
pub mod spawn;

/**
 * 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,
    /// All tasks run in the same OS thread
    SingleThreaded,
}

/**
 * Scheduler configuration options
 *
 * # Fields
 *
 * * sched_mode - The operating mode of the scheduler
 *
 */
pub struct SchedOpts {
    mode: SchedMode,
}

/**
 * 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.
 *
 * * 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.
 *
 * * 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,
    watched: bool,
    indestructible: bool,
    notify_chan: Option<Chan<TaskResult>>,
    name: Option<~str>,
    sched: SchedOpts,
    stack_size: Option<uint>
}

/**
 * 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,
    gen_body: Option<~fn(v: ~fn()) -> ~fn()>,
    can_not_copy: Option<util::NonCopyable>,
    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,
        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;
        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,
                stack_size: self.opts.stack_size
            },
            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) {
        self.opts.watched = true;
    }

    /// Allow the child task to outlive the parent task, at the possible cost
    /// of the parent reporting success even if the child task fails later.
    pub fn unwatched(&mut self) {
        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
     * 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.
     */
    pub fn future_result(&mut self, blk: &fn(v: Port<TaskResult>)) {
        // 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::<TaskResult>();

        blk(notify_pipe_po);

        // Reconfigure self to use a notify channel.
        self.opts.notify_chan = Some(notify_pipe_ch);
    }

    /// Name the task-to-be. Currently the name is used for identification
    /// only in failure messages.
    pub fn name(&mut self, name: ~str) {
        self.opts.name = Some(name);
    }

    /// Configure a custom scheduler mode for the task.
    pub fn sched_mode(&mut self, mode: SchedMode) {
        self.opts.sched.mode = mode;
    }

    /**
     * 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.
     */
    pub fn add_wrapper(&mut self, wrapper: ~fn(v: ~fn()) -> ~fn()) {
        let prev_gen_body = self.gen_body.take();
        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::new(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
        };
        self.gen_body = Some(next_gen_body);
    }

    /**
     * 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.
     */
    pub fn spawn(&mut self, f: ~fn()) {
        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,
            stack_size: x.opts.stack_size
        };
        let f = match gen_body {
            Some(gen) => {
                gen(f)
            }
            None => {
                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: ~fn(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.
     *
     * # 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.
     */
    pub fn try<T:Send>(&mut self, f: ~fn() -> T) -> Result<T,()> {
        let (po, ch) = stream::<T>();
        let mut result = None;

        self.future_result(|r| { result = Some(r); });

        do self.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,
        watched: true,
        indestructible: false,
        notify_chan: None,
        name: None,
        sched: SchedOpts {
            mode: DefaultScheduler,
        },
        stack_size: None
    }
}

/* Spawn convenience functions */

/// 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)`.
pub fn spawn(f: ~fn()) {
    let mut 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: ~fn()) {
    let mut task = task();
    task.unlinked();
    task.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.
     */

    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: ~fn()) {
    let mut task = task();
    task.indestructible();
    task.spawn(f)
}

pub fn spawn_with<A:Send>(arg: A, f: ~fn(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: ~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.
     */

    let mut task = task();
    task.sched_mode(mode);
    task.spawn(f)
}

pub fn try<T:Send>(f: ~fn() -> T) -> Result<T,()> {
    /*!
     * 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.
     */

    let mut task = task();
    task.supervised();
    task.try(f)
}


/* Lifecycle functions */

/// Read the name of the current task.
pub fn with_task_name<U>(blk: &fn(Option<&str>) -> U) -> U {
    use rt::task::Task;

    if in_green_task_context() {
        do Local::borrow::<Task, U> |task| {
            match task.name {
                Some(ref name) => blk(Some(name.as_slice())),
                None => blk(None)
            }
        }
    } else {
        fail!("no task name exists in non-green task context")
    }
}

pub fn deschedule() {
    //! Yield control to the task scheduler

    use rt::local::Local;
    use rt::sched::Scheduler;

    // FIXME #6842: What does yield really mean in newsched?
    // FIXME(#7544): Optimize this, since we know we won't block.
    let sched = Local::take::<Scheduler>();
    do sched.deschedule_running_task_and_then |sched, task| {
        sched.enqueue_blocked_task(task);
    }
}

pub fn failing() -> bool {
    //! True if the running task has failed

    use rt::task::Task;

    do Local::borrow::<Task, bool> |local| {
        local.unwinder.unwinding
    }
}

/**
 * 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: &fn() -> 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 = Local::unsafe_borrow::<Task>();
            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()
        }
    }
}

/// The inverse of unkillable. Only ever to be used nested in unkillable().
pub unsafe fn rekillable<U>(f: &fn() -> U) -> U {
    use rt::task::Task;

    if in_green_task_context() {
        let t = Local::unsafe_borrow::<Task>();
        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] #[ignore(cfg(windows))]
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_newsched_task {
        do task::try {
            do task::spawn {
                fail!();
            }
            do task::unkillable { }
        };
    }
}

#[ignore(reason = "linked failure")]
#[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_newsched_task {
        do task::try {
            do task::unkillable {
                unsafe {
                    do task::rekillable {
                        do task::spawn {
                            fail!();
                        }
                    }
                }
            }
        };
    }
}

#[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.

// !!! These tests are dangerous. If Something is buggy, they will hang, !!!
// !!! instead of exiting cleanly. This might wedge the buildbots.       !!!

#[cfg(test)]
fn block_forever() { let (po, _ch) = stream::<()>(); po.recv(); }

#[ignore(reason = "linked failure")]
#[test] #[ignore(cfg(windows))]
fn test_spawn_unlinked_unsup_no_fail_down() { // grandchild sends on a port
    use rt::test::run_in_newsched_task;
    do run_in_newsched_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] #[ignore(cfg(windows))]
fn test_spawn_unlinked_unsup_no_fail_up() { // child unlinked fails
    use rt::test::run_in_newsched_task;
    do run_in_newsched_task {
        do spawn_unlinked { fail!(); }
    }
}
#[ignore(reason = "linked failure")]
#[test] #[ignore(cfg(windows))]
fn test_spawn_unlinked_sup_no_fail_up() { // child unlinked fails
    use rt::test::run_in_newsched_task;
    do run_in_newsched_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] #[ignore(cfg(windows))]
fn test_spawn_unlinked_sup_fail_down() {
    use rt::test::run_in_newsched_task;
    do run_in_newsched_task {
        let result: Result<(),()> = do try {
            do spawn_supervised { block_forever(); }
            fail!(); // Shouldn't leave a child hanging around.
        };
        assert!(result.is_err());
    }
}

#[ignore(reason = "linked failure")]
#[test] #[ignore(cfg(windows))]
fn test_spawn_linked_sup_fail_up() { // child fails; parent fails
    use rt::test::run_in_newsched_task;
    do run_in_newsched_task {
        let result: Result<(),()> = 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] #[ignore(cfg(windows))]
fn test_spawn_linked_sup_fail_down() { // parent fails; child fails
    use rt::test::run_in_newsched_task;
    do run_in_newsched_task {
        let result: Result<(),()> = 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] #[ignore(cfg(windows))]
fn test_spawn_linked_unsup_fail_up() { // child fails; parent fails
    use rt::test::run_in_newsched_task;
    do run_in_newsched_task {
        let result: Result<(),()> = 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] #[ignore(cfg(windows))]
fn test_spawn_linked_unsup_fail_down() { // parent fails; child fails
    use rt::test::run_in_newsched_task;
    do run_in_newsched_task {
        let result: Result<(),()> = do try {
            // Default options are to spawn linked & unsupervised.
            do spawn { block_forever(); }
            fail!();
        };
        assert!(result.is_err());
    }
}
#[ignore(reason = "linked failure")]
#[test] #[ignore(cfg(windows))]
fn test_spawn_linked_unsup_default_opts() { // parent fails; child fails
    use rt::test::run_in_newsched_task;
    do run_in_newsched_task {
        let result: Result<(),()> = 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] #[ignore(cfg(windows))]
fn test_spawn_failure_propagate_grandchild() {
    use rt::test::run_in_newsched_task;
    do run_in_newsched_task {
        let result: Result<(),()> = 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] #[ignore(cfg(windows))]
fn test_spawn_failure_propagate_secondborn() {
    use rt::test::run_in_newsched_task;
    do run_in_newsched_task {
        let result: Result<(),()> = 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] #[ignore(cfg(windows))]
fn test_spawn_failure_propagate_nephew_or_niece() {
    use rt::test::run_in_newsched_task;
    do run_in_newsched_task {
        let result: Result<(),()> = 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] #[ignore(cfg(windows))]
fn test_spawn_linked_sup_propagate_sibling() {
    use rt::test::run_in_newsched_task;
    do run_in_newsched_task {
        let result: Result<(),()> = 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_newsched_task;

    do run_in_newsched_task {
        do spawn {
            do with_task_name |name| {
                assert!(name.is_none());
            }
        }
    }
}

#[test]
fn test_named_task() {
    use rt::test::run_in_newsched_task;

    do run_in_newsched_task {
        let mut t = task();
        t.name(~"ada lovelace");
        do t.spawn {
            do with_task_name |name| {
                assert!(name.unwrap() == "ada lovelace");
            }
        }
    }
}

#[test]
fn test_run_basic() {
    let (po, ch) = stream::<()>();
    let mut builder = task();
    do builder.spawn {
        ch.send(());
    }
    po.recv();
}

#[cfg(test)]
struct Wrapper {
    f: Option<Chan<()>>
}

#[test]
fn test_add_wrapper() {
    let (po, ch) = stream::<()>();
    let mut b0 = task();
    let ch = Cell::new(ch);
    do b0.add_wrapper |body| {
        let ch = Cell::new(ch.take());
        let result: ~fn() = || {
            let ch = ch.take();
            body();
            ch.send(());
        };
        result
    };
    do b0.spawn { }
    po.recv();
}

#[test]
#[ignore(cfg(windows))]
fn test_future_result() {
    let mut result = None;
    let mut builder = task();
    builder.future_result(|r| result = Some(r));
    do builder.spawn {}
    assert_eq!(result.unwrap().recv(), Success);

    result = None;
    let mut builder = task();
    builder.future_result(|r| result = Some(r));
    builder.unlinked();
    do builder.spawn {
        fail!();
    }
    assert_eq!(result.unwrap().recv(), Failure);
}

#[test] #[should_fail] #[ignore(cfg(windows))]
fn test_back_to_the_future_result() {
    let mut builder = task();
    builder.future_result(util::ignore);
    builder.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!()
    }
}

#[cfg(test)]
fn get_sched_id() -> int {
    do Local::borrow::<::rt::sched::Scheduler, int> |sched| {
        sched.sched_id() as int
    }
}

#[test]
fn test_spawn_sched() {
    let (po, ch) = stream::<()>();
    let ch = SharedChan::new(ch);

    fn f(i: int, ch: SharedChan<()>) {
        let parent_sched_id = get_sched_id();

        do spawn_sched(SingleThreaded) {
            let child_sched_id = 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 = get_sched_id();

    let ch = Cell::new(ch);
    do spawn_sched(SingleThreaded) {
        let parent_sched_id = get_sched_id();
        let ch = Cell::new(ch.take());
        do spawn {
            let ch = ch.take();
            let child_sched_id = get_sched_id();
            assert!(parent_sched_id != child_sched_id);
            assert_eq!(child_sched_id, default_id);
            ch.send(());
        };
    };

    po.recv();
}

#[cfg(test)]
mod testrt {
    use libc;

    externfn!(fn rust_dbg_lock_create() -> *libc::c_void)
    externfn!(fn rust_dbg_lock_destroy(lock: *libc::c_void))
    externfn!(fn rust_dbg_lock_lock(lock: *libc::c_void))
    externfn!(fn rust_dbg_lock_unlock(lock: *libc::c_void))
    externfn!(fn rust_dbg_lock_wait(lock: *libc::c_void))
    externfn!(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
        do 20u.times {
            let (start_po, start_ch) = stream();
            let (fin_po, fin_ch) = stream();

            let lock = testrt::rust_dbg_lock_create();

            do spawn_sched(SingleThreaded) {
                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<int>, ch: &Chan<int>) {
                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::<uint>();

    let x = ~1;
    let x_in_parent = ptr::to_unsafe_ptr(&*x) as uint;

    do spawnfn || {
        let x_in_child = ptr::to_unsafe_ptr(&*x) as uint;
        ch.send(x_in_child);
    }

    let x_in_child = p.recv();
    assert_eq!(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| {
        let mut builder = task();
        do builder.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();
        }
    }
}

#[ignore(reason = "linked failure")]
#[test]
#[ignore(cfg(windows))]
#[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]
#[ignore(cfg(windows))]
#[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)
    // (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 {
                let mut t = task();
                t.unwatched();
                t.spawn(child_no(x+1));
            }
        }
    }
    let mut t = task();
    t.unwatched();
    t.spawn(child_no(0));
}

#[test]
fn test_simple_newsched_spawn() {
    use rt::test::run_in_newsched_task;

    do run_in_newsched_task {
        spawn(||())
    }
}

#[ignore(reason = "linked failure")]
#[test] #[ignore(cfg(windows))]
fn test_spawn_watched() {
    use rt::test::run_in_newsched_task;
    do run_in_newsched_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] #[ignore(cfg(windows))]
fn test_indestructible() {
    use rt::test::run_in_newsched_task;
    do run_in_newsched_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());
    }
}
