summary refs log tree commit diff
path: root/src/libstd/task
diff options
context:
space:
mode:
authorPatrick Walton <pcwalton@mimiga.net>2013-05-17 10:45:09 -0700
committerPatrick Walton <pcwalton@mimiga.net>2013-05-22 21:57:05 -0700
commit0c820d4123c754522b0655e9e74f692c55685bfa (patch)
tree7dbb86c30b451217b4e8f75173043744fe3255ff /src/libstd/task
parent565942b145efbf6c1d1f66db46423d721b55d32c (diff)
downloadrust-0c820d4123c754522b0655e9e74f692c55685bfa.tar.gz
rust-0c820d4123c754522b0655e9e74f692c55685bfa.zip
libstd: Rename libcore to libstd and libstd to libextra; update makefiles.
This only changes the directory names; it does not change the "real"
metadata names.
Diffstat (limited to 'src/libstd/task')
-rw-r--r--src/libstd/task/local_data_priv.rs235
-rw-r--r--src/libstd/task/mod.rs1189
-rw-r--r--src/libstd/task/rt.rs71
-rw-r--r--src/libstd/task/spawn.rs791
4 files changed, 2286 insertions, 0 deletions
diff --git a/src/libstd/task/local_data_priv.rs b/src/libstd/task/local_data_priv.rs
new file mode 100644
index 00000000000..2f97eaacf4b
--- /dev/null
+++ b/src/libstd/task/local_data_priv.rs
@@ -0,0 +1,235 @@
+// 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 <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.
+
+#[doc(hidden)]; // FIXME #3538
+
+use cast;
+use cmp::Eq;
+use libc;
+use prelude::*;
+use task::rt;
+use local_data::LocalDataKey;
+
+use super::rt::rust_task;
+use rt::task::{Task, LocalStorage};
+
+pub enum Handle {
+    OldHandle(*rust_task),
+    NewHandle(*mut LocalStorage)
+}
+
+impl Handle {
+    pub fn new() -> Handle {
+        use rt::{context, OldTaskContext};
+        use rt::local::Local;
+        unsafe {
+            match context() {
+                OldTaskContext => {
+                    OldHandle(rt::rust_get_task())
+                }
+                _ => {
+                    let task = Local::unsafe_borrow::<Task>();
+                    NewHandle(&mut (*task).storage)
+                }
+            }
+        }
+    }
+}
+
+pub trait LocalData { }
+impl<T: 'static> LocalData for @T { }
+
+impl Eq for @LocalData {
+    fn eq(&self, other: &@LocalData) -> bool {
+        unsafe {
+            let ptr_a: &(uint, uint) = cast::transmute(self);
+            let ptr_b: &(uint, uint) = cast::transmute(other);
+            return ptr_a == ptr_b;
+        }
+    }
+    fn ne(&self, other: &@LocalData) -> bool { !(*self).eq(other) }
+}
+
+// If TLS is used heavily in future, this could be made more efficient with a
+// proper map.
+type TaskLocalElement = (*libc::c_void, *libc::c_void, @LocalData);
+// Has to be a pointer at outermost layer; the foreign call returns void *.
+type TaskLocalMap = @mut ~[Option<TaskLocalElement>];
+
+fn cleanup_task_local_map(map_ptr: *libc::c_void) {
+    unsafe {
+        assert!(!map_ptr.is_null());
+        // Get and keep the single reference that was created at the
+        // beginning.
+        let _map: TaskLocalMap = cast::transmute(map_ptr);
+        // All local_data will be destroyed along with the map.
+    }
+}
+
+// Gets the map from the runtime. Lazily initialises if not done so already.
+unsafe fn get_local_map(handle: Handle) -> TaskLocalMap {
+    match handle {
+        OldHandle(task) => get_task_local_map(task),
+        NewHandle(local_storage) => get_newsched_local_map(local_storage)
+    }
+}
+
+unsafe fn get_task_local_map(task: *rust_task) -> TaskLocalMap {
+
+    extern fn cleanup_task_local_map_extern_cb(map_ptr: *libc::c_void) {
+        cleanup_task_local_map(map_ptr);
+    }
+
+    // Relies on the runtime initialising the pointer to null.
+    // Note: The map's box lives in TLS invisibly referenced once. Each time
+    // we retrieve it for get/set, we make another reference, which get/set
+    // drop when they finish. No "re-storing after modifying" is needed.
+    let map_ptr = rt::rust_get_task_local_data(task);
+    if map_ptr.is_null() {
+        let map: TaskLocalMap = @mut ~[];
+        // NB: This bumps the ref count before converting to an unsafe pointer,
+        // keeping the map alive until TLS is destroyed
+        rt::rust_set_task_local_data(task, cast::transmute(map));
+        rt::rust_task_local_data_atexit(task, cleanup_task_local_map_extern_cb);
+        map
+    } else {
+        let map = cast::transmute(map_ptr);
+        let nonmut = cast::transmute::<TaskLocalMap,
+                                       @~[Option<TaskLocalElement>]>(map);
+        cast::bump_box_refcount(nonmut);
+        map
+    }
+}
+
+unsafe fn get_newsched_local_map(local: *mut LocalStorage) -> TaskLocalMap {
+    match &mut *local {
+        &LocalStorage(map_ptr, Some(_)) => {
+            assert!(map_ptr.is_not_null());
+            let map = cast::transmute(map_ptr);
+            let nonmut = cast::transmute::<TaskLocalMap,
+            @~[Option<TaskLocalElement>]>(map);
+            cast::bump_box_refcount(nonmut);
+            return map;
+        }
+        &LocalStorage(ref mut map_ptr, ref mut at_exit) => {
+            assert!((*map_ptr).is_null());
+            let map: TaskLocalMap = @mut ~[];
+            *map_ptr = cast::transmute(map);
+            let at_exit_fn: ~fn(*libc::c_void) = |p|cleanup_task_local_map(p);
+            *at_exit = Some(at_exit_fn);
+            return map;
+        }
+    }
+}
+
+unsafe fn key_to_key_value<T: 'static>(key: LocalDataKey<T>) -> *libc::c_void {
+    // Keys are closures, which are (fnptr,envptr) pairs. Use fnptr.
+    // Use reinterpret_cast -- transmute would leak (forget) the closure.
+    let pair: (*libc::c_void, *libc::c_void) = cast::transmute_copy(&key);
+    pair.first()
+}
+
+// If returning Some(..), returns with @T with the map's reference. Careful!
+unsafe fn local_data_lookup<T: 'static>(
+    map: TaskLocalMap, key: LocalDataKey<T>)
+    -> Option<(uint, *libc::c_void)> {
+
+    let key_value = key_to_key_value(key);
+    let map_pos = (*map).position(|entry|
+        match *entry {
+            Some((k,_,_)) => k == key_value,
+            None => false
+        }
+    );
+    do map_pos.map |index| {
+        // .get() is guaranteed because of "None { false }" above.
+        let (_, data_ptr, _) = (*map)[*index].get();
+        (*index, data_ptr)
+    }
+}
+
+unsafe fn local_get_helper<T: 'static>(
+    handle: Handle, key: LocalDataKey<T>,
+    do_pop: bool) -> Option<@T> {
+
+    let map = get_local_map(handle);
+    // Interpreturn our findings from the map
+    do local_data_lookup(map, key).map |result| {
+        // A reference count magically appears on 'data' out of thin air. It
+        // was referenced in the local_data box, though, not here, so before
+        // overwriting the local_data_box we need to give an extra reference.
+        // We must also give an extra reference when not removing.
+        let (index, data_ptr) = *result;
+        let data: @T = cast::transmute(data_ptr);
+        cast::bump_box_refcount(data);
+        if do_pop {
+            map[index] = None;
+        }
+        data
+    }
+}
+
+
+pub unsafe fn local_pop<T: 'static>(
+    handle: Handle,
+    key: LocalDataKey<T>) -> Option<@T> {
+
+    local_get_helper(handle, key, true)
+}
+
+pub unsafe fn local_get<T: 'static>(
+    handle: Handle,
+    key: LocalDataKey<T>) -> Option<@T> {
+
+    local_get_helper(handle, key, false)
+}
+
+pub unsafe fn local_set<T: 'static>(
+    handle: Handle, key: LocalDataKey<T>, data: @T) {
+
+    let map = get_local_map(handle);
+    // Store key+data as *voids. Data is invisibly referenced once; key isn't.
+    let keyval = key_to_key_value(key);
+    // We keep the data in two forms: one as an unsafe pointer, so we can get
+    // it back by casting; another in an existential box, so the reference we
+    // own on it can be dropped when the box is destroyed. The unsafe pointer
+    // does not have a reference associated with it, so it may become invalid
+    // when the box is destroyed.
+    let data_ptr = *cast::transmute::<&@T, &*libc::c_void>(&data);
+    let data_box = @data as @LocalData;
+    // Construct new entry to store in the map.
+    let new_entry = Some((keyval, data_ptr, data_box));
+    // Find a place to put it.
+    match local_data_lookup(map, key) {
+        Some((index, _old_data_ptr)) => {
+            // Key already had a value set, _old_data_ptr, whose reference
+            // will get dropped when the local_data box is overwritten.
+            map[index] = new_entry;
+        }
+        None => {
+            // Find an empty slot. If not, grow the vector.
+            match (*map).position(|x| x.is_none()) {
+                Some(empty_index) => { map[empty_index] = new_entry; }
+                None => { map.push(new_entry); }
+            }
+        }
+    }
+}
+
+pub unsafe fn local_modify<T: 'static>(
+    handle: Handle, key: LocalDataKey<T>,
+    modify_fn: &fn(Option<@T>) -> Option<@T>) {
+
+    // Could be more efficient by doing the lookup work, but this is easy.
+    let newdata = modify_fn(local_pop(handle, key));
+    if newdata.is_some() {
+        local_set(handle, key, newdata.unwrap());
+    }
+}
diff --git a/src/libstd/task/mod.rs b/src/libstd/task/mod.rs
new file mode 100644
index 00000000000..490a69248ee
--- /dev/null
+++ b/src/libstd/task/mod.rs
@@ -0,0 +1,1189 @@
+// 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 <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!");
+ * }
+ * ~~~
+ */
+
+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};
+use util;
+use util::replace;
+use unstable::finally::Finally;
+use rt::{context, OldTaskContext};
+
+#[cfg(test)] use comm::SharedChan;
+
+mod local_data_priv;
+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<uint>,
+}
+
+/**
+ * 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,
+    notify_chan: Option<Chan<TaskResult>>,
+    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,
+    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,
+    }
+}
+
+#[doc(hidden)] // FIXME #3538
+priv 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 = 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(&mut self) {
+        self.opts.linked = 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.
+    fn supervised(&mut self) {
+        self.opts.supervised = true;
+        self.opts.linked = false;
+    }
+
+    /// Link the child task's and parent task's failures. If either fails, the
+    /// other will be killed.
+    fn linked(&mut self) {
+        self.opts.linked = true;
+        self.opts.supervised = false;
+    }
+
+    /**
+     * 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(&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);
+    }
+
+    /// Configure a custom scheduler mode for the task.
+    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.
+     */
+    fn add_wrapper(&mut self, wrapper: ~fn(v: ~fn()) -> ~fn()) {
+        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
+        };
+        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.
+     */
+    fn spawn(&mut 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<A:Owned>(&mut 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<T:Owned>(&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,
+        notify_chan: None,
+        sched: SchedOpts {
+            mode: DefaultScheduler,
+            foreign_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)
+}
+
+pub fn spawn_with<A:Owned>(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)`.
+     */
+
+    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:Owned>(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 */
+
+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
+
+    use rt::{context, OldTaskContext};
+    use rt::local::Local;
+    use rt::task::Task;
+
+    match context() {
+        OldTaskContext => {
+            unsafe {
+                rt::rust_task_is_unwinding(rt::rust_get_task())
+            }
+        }
+        _ => {
+            let mut unwinding = false;
+            do Local::borrow::<Task> |local| {
+                unwinding = match local.unwinder {
+                    Some(unwinder) => {
+                        unwinder.unwinding
+                    }
+                    None => {
+                        // Because there is no unwinder we can't be unwinding.
+                        // (The process will abort on failure)
+                        false
+                    }
+                }
+            }
+            return unwinding;
+        }
+    }
+}
+
+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<U>(f: &fn() -> U) -> U {
+    if context() == OldTaskContext {
+        let t = rt::rust_get_task();
+        do (|| {
+            rt::rust_task_inhibit_kill(t);
+            f()
+        }).finally {
+            rt::rust_task_allow_kill(t);
+        }
+    } else {
+        // FIXME #6377
+        f()
+    }
+}
+
+/// The inverse of unkillable. Only ever to be used nested in unkillable().
+pub unsafe fn rekillable<U>(f: &fn() -> U) -> U {
+    if context() == OldTaskContext {
+        let t = rt::rust_get_task();
+        do (|| {
+            rt::rust_task_allow_kill(t);
+            f()
+        }).finally {
+            rt::rust_task_inhibit_kill(t);
+        }
+    } else {
+        // FIXME #6377
+        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<U>(f: &fn() -> U) -> U {
+    if context() == OldTaskContext {
+        let t = rt::rust_get_task();
+        do (|| {
+            rt::rust_task_inhibit_kill(t);
+            rt::rust_task_inhibit_yield(t);
+            f()
+        }).finally {
+            rt::rust_task_allow_yield(t);
+            rt::rust_task_allow_kill(t);
+        }
+    } else {
+        // FIXME #6377
+        f()
+    }
+}
+
+#[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.       !!!
+
+#[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 16.times { 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 16.times { 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 mut b0 = task();
+    b0.opts.linked = true;
+    b0.opts.supervised = true;
+
+    do b0.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 mut b0 = task();
+    b0.opts.linked = true;
+    b0.opts.supervised = true;
+    do b0.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.
+    let mut builder = task();
+    builder.linked();
+    do builder.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 16.times { 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 16.times { 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 16.times { 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 16.times { task::yield(); }
+    fail!();
+}
+
+#[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(ch);
+    do b0.add_wrapper |body| {
+        let ch = Cell(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!()
+    }
+}
+
+#[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 = Cell(ch);
+    do spawn_sched(SingleThreaded) {
+        let parent_sched_id = unsafe { rt::rust_get_sched_id() };
+        let ch = Cell(ch.take());
+        do spawn {
+            let ch = ch.take();
+            let child_sched_id = unsafe { rt::rust_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;
+
+    #[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 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) {
+                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<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();
+        }
+    }
+}
+
+#[test]
+fn test_platform_thread() {
+    let (po, ch) = stream();
+    let mut builder = task();
+    builder.sched_mode(PlatformThread);
+    do builder.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 10.times { 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 10.times { 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_eq!(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_eq!(max_threads as int, 2);
+            let running_threads = rt::rust_sched_current_nonlazy_threads();
+            assert_eq!(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_eq!(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(||())
+    }
+}
diff --git a/src/libstd/task/rt.rs b/src/libstd/task/rt.rs
new file mode 100644
index 00000000000..760812252bc
--- /dev/null
+++ b/src/libstd/task/rt.rs
@@ -0,0 +1,71 @@
+// 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 <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.
+
+/*!
+
+The task interface to the runtime
+
+*/
+
+#[doc(hidden)]; // FIXME #3538
+
+use libc;
+
+#[allow(non_camel_case_types)] // runtime type
+pub type sched_id = int;
+#[allow(non_camel_case_types)] // runtime type
+pub type task_id = int;
+
+// These are both opaque runtime/compiler types that we don't know the
+// structure of and should only deal with via unsafe pointer
+#[allow(non_camel_case_types)] // runtime type
+pub type rust_task = libc::c_void;
+#[allow(non_camel_case_types)] // runtime type
+pub type rust_closure = libc::c_void;
+
+pub extern {
+    #[rust_stack]
+    fn rust_task_yield(task: *rust_task) -> bool;
+
+    fn rust_get_sched_id() -> sched_id;
+    fn rust_new_sched(num_threads: libc::uintptr_t) -> sched_id;
+    fn rust_sched_threads() -> libc::size_t;
+    fn rust_sched_current_nonlazy_threads() -> libc::size_t;
+    fn rust_num_threads() -> libc::uintptr_t;
+
+    fn get_task_id() -> task_id;
+    #[rust_stack]
+    fn rust_get_task() -> *rust_task;
+
+    fn new_task() -> *rust_task;
+    fn rust_new_task_in_sched(id: sched_id) -> *rust_task;
+
+    fn start_task(task: *rust_task, closure: *rust_closure);
+
+    fn rust_task_is_unwinding(task: *rust_task) -> bool;
+    fn rust_osmain_sched_id() -> sched_id;
+    #[rust_stack]
+    fn rust_task_inhibit_kill(t: *rust_task);
+    #[rust_stack]
+    fn rust_task_allow_kill(t: *rust_task);
+    #[rust_stack]
+    fn rust_task_inhibit_yield(t: *rust_task);
+    #[rust_stack]
+    fn rust_task_allow_yield(t: *rust_task);
+    fn rust_task_kill_other(task: *rust_task);
+    fn rust_task_kill_all(task: *rust_task);
+
+    #[rust_stack]
+    fn rust_get_task_local_data(task: *rust_task) -> *libc::c_void;
+    #[rust_stack]
+    fn rust_set_task_local_data(task: *rust_task, map: *libc::c_void);
+    #[rust_stack]
+    fn rust_task_local_data_atexit(task: *rust_task, cleanup_fn: *u8);
+}
diff --git a/src/libstd/task/spawn.rs b/src/libstd/task/spawn.rs
new file mode 100644
index 00000000000..81e5af5caab
--- /dev/null
+++ b/src/libstd/task/spawn.rs
@@ -0,0 +1,791 @@
+// 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 <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.
+
+/*!**************************************************************************
+ * Spawning & linked failure
+ *
+ * Several data structures are involved in task management to allow properly
+ * propagating failure across linked/supervised tasks.
+ *
+ * (1) The "taskgroup_arc" is an unsafe::exclusive which contains a hashset of
+ *     all tasks that are part of the group. Some tasks are 'members', which
+ *     means if they fail, they will kill everybody else in the taskgroup.
+ *     Other tasks are 'descendants', which means they will not kill tasks
+ *     from this group, but can be killed by failing members.
+ *
+ *     A new one of these is created each spawn_linked or spawn_supervised.
+ *
+ * (2) The "tcb" is a per-task control structure that tracks a task's spawn
+ *     configuration. It contains a reference to its taskgroup_arc, a
+ *     reference to its node in the ancestor list (below), a flag for
+ *     whether it's part of the 'main'/'root' taskgroup, and an optionally
+ *     configured notification port. These are stored in TLS.
+ *
+ * (3) The "ancestor_list" is a cons-style list of unsafe::exclusives which
+ *     tracks 'generations' of taskgroups -- a group's ancestors are groups
+ *     which (directly or transitively) spawn_supervised-ed them. Each task
+ *     is recorded in the 'descendants' of each of its ancestor groups.
+ *
+ *     Spawning a supervised task is O(n) in the number of generations still
+ *     alive, and exiting (by success or failure) that task is also O(n).
+ *
+ * This diagram depicts the references between these data structures:
+ *
+ *          linked_________________________________
+ *        ___/                   _________         \___
+ *       /   \                  | group X |        /   \
+ *      (  A  ) - - - - - - - > | {A,B} {}|< - - -(  B  )
+ *       \___/                  |_________|        \___/
+ *      unlinked
+ *         |      __ (nil)
+ *         |      //|                         The following code causes this:
+ *         |__   //   /\         _________
+ *        /   \ //    ||        | group Y |     fn taskA() {
+ *       (  C  )- - - ||- - - > |{C} {D,E}|         spawn(taskB);
+ *        \___/      /  \=====> |_________|         spawn_unlinked(taskC);
+ *      supervise   /gen \                          ...
+ *         |    __  \ 00 /                      }
+ *         |    //|  \__/                       fn taskB() { ... }
+ *         |__ //     /\         _________      fn taskC() {
+ *        /   \/      ||        | group Z |         spawn_supervised(taskD);
+ *       (  D  )- - - ||- - - > | {D} {E} |         ...
+ *        \___/      /  \=====> |_________|     }
+ *      supervise   /gen \                      fn taskD() {
+ *         |    __  \ 01 /                          spawn_supervised(taskE);
+ *         |    //|  \__/                           ...
+ *         |__ //                _________      }
+ *        /   \/                | group W |     fn taskE() { ... }
+ *       (  E  )- - - - - - - > | {E}  {} |
+ *        \___/                 |_________|
+ *
+ *        "tcb"               "taskgroup_arc"
+ *             "ancestor_list"
+ *
+ ****************************************************************************/
+
+#[doc(hidden)]; // FIXME #3538
+
+use cast::transmute;
+use cast;
+use cell::Cell;
+use container::Map;
+use comm::{Chan, GenericChan};
+use prelude::*;
+use ptr;
+use hashmap::HashSet;
+use task::local_data_priv::{local_get, local_set, OldHandle};
+use task::rt::rust_task;
+use task::rt;
+use task::{Failure, ManualThreads, PlatformThread, SchedOpts, SingleThreaded};
+use task::{Success, TaskOpts, TaskResult, ThreadPerCore, ThreadPerTask};
+use task::{ExistingScheduler, SchedulerHandle};
+use task::unkillable;
+use uint;
+use util;
+use unstable::sync::{Exclusive, exclusive};
+use rt::local::Local;
+
+#[cfg(test)] use task::default_task_opts;
+
+macro_rules! move_it (
+    { $x:expr } => ( unsafe { let y = *ptr::to_unsafe_ptr(&($x)); y } )
+)
+
+type TaskSet = HashSet<*rust_task>;
+
+fn new_taskset() -> TaskSet {
+    HashSet::new()
+}
+fn taskset_insert(tasks: &mut TaskSet, task: *rust_task) {
+    let didnt_overwrite = tasks.insert(task);
+    assert!(didnt_overwrite);
+}
+fn taskset_remove(tasks: &mut TaskSet, task: *rust_task) {
+    let was_present = tasks.remove(&task);
+    assert!(was_present);
+}
+pub fn taskset_each(tasks: &TaskSet, blk: &fn(v: *rust_task) -> bool) -> bool {
+    tasks.each(|k| blk(*k))
+}
+
+// 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 {
+    (&const 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.
+    // FIXME(#3068): Make the generation counter togglable with #[cfg(debug)].
+    generation:     uint,
+    // Should really be a non-option. This way appeases borrowck.
+    parent_group:   Option<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(always)]
+fn access_group<U>(x: &TaskGroupArc, blk: &fn(TaskGroupInner) -> U) -> U {
+    x.with(blk)
+}
+
+#[inline(always)]
+fn access_ancestors<U>(x: &Exclusive<AncestorNode>,
+                       blk: &fn(x: &mut AncestorNode) -> U) -> U {
+    x.with(blk)
+}
+
+// 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.
+// FIXME(#2190): Change Option<@fn(...)> to Option<&fn(...)>, to save on
+// allocations. Once that bug is fixed, changing the sigil should suffice.
+fn each_ancestor(list:        &mut AncestorList,
+                 bail_opt:    Option<@fn(TaskGroupInner)>,
+                 forward_blk: &fn(TaskGroupInner) -> bool)
+              -> bool {
+    // "Kickoff" call - there was no last generation.
+    return !coalesce(list, bail_opt, 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_opt:        Option<@fn(TaskGroupInner)>,
+                forward_blk:     &fn(TaskGroupInner) -> bool,
+                last_generation: uint) -> bool {
+        // Need to swap the list out to use it, to appease borrowck.
+        let tmp_list = util::replace(&mut *list, AncestorList(None));
+        let (coalesce_this, early_break) =
+            iterate(&tmp_list, bail_opt, 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();
+        } else {
+            // No coalesce; restore from tmp. ("next = old_next;")
+            *list = tmp_list;
+        }
+        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:       &AncestorList,
+               bail_opt:        Option<@fn(TaskGroupInner)>,
+               forward_blk:     &fn(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?)
+
+        // 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.
+        return do (**ancestors).map_default((None,false)) |ancestor_arc| {
+            // NB: Takes a lock! (this ancestor node)
+            do access_ancestors(ancestor_arc) |nobe| {
+                // Check monotonicity
+                assert!(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 with_parent_tg(&mut 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 *rust_task
+                        // 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 (foreach) 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, bail_opt,
+                                           forward_blk, nobe.generation);
+                }
+                /*##########################################################*
+                 * Step 3: Maybe unwind; compute return info for our caller.
+                 *##########################################################*/
+                if need_unwind && !nobe_is_dead {
+                    for bail_opt.each |bail_blk| {
+                        do with_parent_tg(&mut 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)
+                }
+            }
+        };
+
+        // Wrapper around exclusive::with that appeases borrowck.
+        fn with_parent_tg<U>(parent_group: &mut Option<TaskGroupArc>,
+                             blk: &fn(TaskGroupInner) -> U) -> U {
+            // If this trips, more likely the problem is 'blk' failed inside.
+            let tmp_arc = parent_group.swap_unwrap();
+            let result = do access_group(&tmp_arc) |tg_opt| { blk(tg_opt) };
+            *parent_group = Some(tmp_arc);
+            result
+        }
+    }
+}
+
+// One of these per task.
+struct TCB {
+    me:         *rust_task,
+    // List of tasks with whose fates this one's is intertwined.
+    tasks:      TaskGroupArc, // 'none' means the group has failed.
+    // Lists of tasks who will kill us if they fail, but whom we won't kill.
+    ancestors:  AncestorList,
+    is_main:    bool,
+    notifier:   Option<AutoNotify>,
+}
+
+impl Drop for TCB {
+    // Runs on task exit.
+    fn finalize(&self) {
+        unsafe {
+            let this: &mut TCB = transmute(self);
+
+            // If we are failing, the whole taskgroup needs to die.
+            if rt::rust_task_is_unwinding(self.me) {
+                for this.notifier.each_mut |x| {
+                    x.failed = true;
+                }
+                // Take everybody down with us.
+                do access_group(&self.tasks) |tg| {
+                    kill_taskgroup(tg, self.me, self.is_main);
+                }
+            } else {
+                // Remove ourselves from the group(s).
+                do access_group(&self.tasks) |tg| {
+                    leave_taskgroup(tg, self.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.
+            for each_ancestor(&mut this.ancestors, None) |ancestor_group| {
+                leave_taskgroup(ancestor_group, self.me, false);
+            };
+        }
+    }
+}
+
+fn TCB(me: *rust_task,
+       tasks: TaskGroupArc,
+       ancestors: AncestorList,
+       is_main: bool,
+       mut notifier: Option<AutoNotify>) -> TCB {
+    for notifier.each_mut |x| {
+        x.failed = false;
+    }
+
+    TCB {
+        me: me,
+        tasks: tasks,
+        ancestors: ancestors,
+        is_main: is_main,
+        notifier: notifier
+    }
+}
+
+struct AutoNotify {
+    notify_chan: Chan<TaskResult>,
+    failed: bool,
+}
+
+impl Drop for AutoNotify {
+    fn finalize(&self) {
+        let result = if self.failed { Failure } else { Success };
+        self.notify_chan.send(result);
+    }
+}
+
+fn AutoNotify(chan: Chan<TaskResult>) -> AutoNotify {
+    AutoNotify {
+        notify_chan: chan,
+        failed: true // Un-set above when taskgroup successfully made.
+    }
+}
+
+fn enlist_in_taskgroup(state: TaskGroupInner, me: *rust_task,
+                           is_member: bool) -> bool {
+    let newstate = util::replace(&mut *state, None);
+    // If 'None', the group was failing. Can't enlist.
+    if newstate.is_some() {
+        let mut group = newstate.unwrap();
+        taskset_insert(if is_member {
+            &mut group.members
+        } else {
+            &mut group.descendants
+        }, me);
+        *state = Some(group);
+        true
+    } else {
+        false
+    }
+}
+
+// NB: Runs in destructor/post-exit context. Can't 'fail'.
+fn leave_taskgroup(state: TaskGroupInner, me: *rust_task,
+                       is_member: bool) {
+    let newstate = util::replace(&mut *state, None);
+    // If 'None', already failing and we've already gotten a kill signal.
+    if newstate.is_some() {
+        let mut group = newstate.unwrap();
+        taskset_remove(if is_member {
+            &mut group.members
+        } else {
+            &mut group.descendants
+        }, me);
+        *state = Some(group);
+    }
+}
+
+// NB: Runs in destructor/post-exit context. Can't 'fail'.
+fn kill_taskgroup(state: TaskGroupInner, me: *rust_task, is_main: bool) {
+    unsafe {
+        // NB: We could do the killing iteration outside of the group arc, by
+        // having "let mut newstate" here, swapping inside, and iterating
+        // after. But that would let other exiting tasks fall-through and exit
+        // while we were trying to kill them, causing potential
+        // use-after-free. A task's presence in the arc guarantees it's alive
+        // only while we hold the lock, so if we're failing, all concurrently
+        // exiting tasks must wait for us. To do it differently, we'd have to
+        // use the runtime's task refcounting, but that could leave task
+        // structs around long after their task exited.
+        let newstate = util::replace(state, None);
+        // 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.)
+        if newstate.is_some() {
+            let group = newstate.unwrap();
+            for taskset_each(&group.members) |sibling| {
+                // Skip self - killing ourself won't do much good.
+                if sibling != me {
+                    rt::rust_task_kill_other(sibling);
+                }
+            }
+            for taskset_each(&group.descendants) |child| {
+                assert!(child != me);
+                rt::rust_task_kill_other(child);
+            }
+            // Only one task should ever do this.
+            if is_main {
+                rt::rust_task_kill_all(me);
+            }
+            // Do NOT restore state to Some(..)! It stays None to indicate
+            // that the whole taskgroup is failing, to forbid new spawns.
+        }
+        // (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.
+macro_rules! taskgroup_key (
+    // Use a "code pointer" value that will never be a real code pointer.
+    () => (cast::transmute((-2 as uint, 0u)))
+)
+
+fn gen_child_taskgroup(linked: bool, supervised: bool)
+    -> (TaskGroupArc, AncestorList, bool) {
+    unsafe {
+        let spawner = rt::rust_get_task();
+        /*##################################################################*
+         * Step 1. Get spawner's taskgroup info.
+         *##################################################################*/
+        let spawner_group: @@mut TCB =
+            match local_get(OldHandle(spawner), taskgroup_key!()) {
+                None => {
+                    // Main task, doing first spawn ever. Lazily initialise
+                    // here.
+                    let mut members = new_taskset();
+                    taskset_insert(&mut members, spawner);
+                    let tasks = exclusive(Some(TaskGroupData {
+                        members: members,
+                        descendants: new_taskset(),
+                    }));
+                    // Main task/group has no ancestors, no notifier, etc.
+                    let group = @@mut TCB(spawner,
+                                          tasks,
+                                          AncestorList(None),
+                                          true,
+                                          None);
+                    local_set(OldHandle(spawner), taskgroup_key!(), group);
+                    group
+                }
+                Some(group) => group
+            };
+        let spawner_group: &mut TCB = *spawner_group;
+
+        /*##################################################################*
+         * Step 2. Process spawn options for child.
+         *##################################################################*/
+        return if linked {
+            // Child is in the same group as spawner.
+            let g = spawner_group.tasks.clone();
+            // Child's ancestors are spawner's ancestors.
+            let a = share_ancestors(&mut spawner_group.ancestors);
+            // Propagate main-ness.
+            (g, a, spawner_group.is_main)
+        } else {
+            // Child is in a separate group from spawner.
+            let g = exclusive(Some(TaskGroupData {
+                members:     new_taskset(),
+                descendants: new_taskset(),
+            }));
+            let a = if supervised {
+                // Child's ancestors start with the spawner.
+                let old_ancestors =
+                    share_ancestors(&mut spawner_group.ancestors);
+                // FIXME(#3068) - The generation counter is only used for a
+                // debug assertion, but initialising it requires locking a
+                // mutex. Hence it should be enabled only in debug builds.
+                let new_generation =
+                    match *old_ancestors {
+                        Some(ref arc) => {
+                            access_ancestors(arc, |a| a.generation+1)
+                        }
+                        None => 0 // the actual value doesn't really matter.
+                    };
+                assert!(new_generation < uint::max_value);
+                // Build a new node in the ancestor list.
+                AncestorList(Some(exclusive(AncestorNode {
+                    generation: new_generation,
+                    parent_group: Some(spawner_group.tasks.clone()),
+                    ancestors: old_ancestors,
+                })))
+            } else {
+                // Child has no ancestors.
+                AncestorList(None)
+            };
+            (g, a, false)
+        };
+    }
+
+    fn share_ancestors(ancestors: &mut AncestorList) -> AncestorList {
+        // Appease the borrow-checker. Really this wants to be written as:
+        // match ancestors
+        //    Some(ancestor_arc) { ancestor_list(Some(ancestor_arc.clone())) }
+        //    None               { ancestor_list(None) }
+        let tmp = util::replace(&mut **ancestors, None);
+        if tmp.is_some() {
+            let ancestor_arc = tmp.unwrap();
+            let result = ancestor_arc.clone();
+            **ancestors = Some(ancestor_arc);
+            AncestorList(Some(result))
+        } else {
+            AncestorList(None)
+        }
+    }
+}
+
+pub fn spawn_raw(opts: TaskOpts, f: ~fn()) {
+    use rt::*;
+
+    match context() {
+        OldTaskContext => {
+            spawn_raw_oldsched(opts, f)
+        }
+        TaskContext => {
+            spawn_raw_newsched(opts, f)
+        }
+        SchedulerContext => {
+            fail!("can't spawn from scheduler context")
+        }
+        GlobalContext => {
+            fail!("can't spawn from global context")
+        }
+    }
+}
+
+fn spawn_raw_newsched(_opts: TaskOpts, f: ~fn()) {
+    use rt::sched::*;
+
+    let mut sched = Local::take::<Scheduler>();
+    let task = ~Coroutine::new(&mut sched.stack_pool, f);
+    sched.schedule_new_task(task);
+}
+
+fn spawn_raw_oldsched(mut opts: TaskOpts, f: ~fn()) {
+
+    let (child_tg, ancestors, is_main) =
+        gen_child_taskgroup(opts.linked, opts.supervised);
+
+    unsafe {
+        let child_data = Cell((child_tg, ancestors, f));
+        // Being killed with the unsafe task/closure pointers would leak them.
+        do unkillable {
+            // Agh. Get move-mode items into the closure. FIXME (#2829)
+            let (child_tg, ancestors, f) = child_data.take();
+            // Create child task.
+            let new_task = match opts.sched.mode {
+                DefaultScheduler => rt::new_task(),
+                _ => new_task_in_sched(opts.sched)
+            };
+            assert!(!new_task.is_null());
+            // Getting killed after here would leak the task.
+            let notify_chan = if opts.notify_chan.is_none() {
+                None
+            } else {
+                Some(opts.notify_chan.swap_unwrap())
+            };
+
+            let child_wrapper = make_child_wrapper(new_task, child_tg,
+                  ancestors, is_main, notify_chan, f);
+
+            let closure = cast::transmute(&child_wrapper);
+
+            // Getting killed between these two calls would free the child's
+            // closure. (Reordering them wouldn't help - then getting killed
+            // between them would leak.)
+            rt::start_task(new_task, closure);
+            cast::forget(child_wrapper);
+        }
+    }
+
+    // This function returns a closure-wrapper that we pass to the child task.
+    // (1) It sets up the notification channel.
+    // (2) It attempts to enlist in the child's group and all ancestor groups.
+    // (3a) If any of those fails, it leaves all groups, and does nothing.
+    // (3b) Otherwise it builds a task control structure and puts it in TLS,
+    // (4) ...and runs the provided body function.
+    fn make_child_wrapper(child: *rust_task, child_arc: TaskGroupArc,
+                          ancestors: AncestorList, is_main: bool,
+                          notify_chan: Option<Chan<TaskResult>>,
+                          f: ~fn())
+                       -> ~fn() {
+        let child_data = Cell((child_arc, ancestors));
+        let result: ~fn() = || {
+            // Agh. Get move-mode items into the closure. FIXME (#2829)
+            let mut (child_arc, ancestors) = child_data.take();
+            // Child task runs this code.
+
+            // Even if the below code fails to kick the child off, we must
+            // send Something on the notify channel.
+
+            //let mut notifier = None;//notify_chan.map(|c| AutoNotify(c));
+            let notifier = match notify_chan {
+                Some(ref notify_chan_value) => {
+                    let moved_ncv = move_it!(*notify_chan_value);
+                    Some(AutoNotify(moved_ncv))
+                }
+                _ => None
+            };
+
+            if enlist_many(child, &child_arc, &mut ancestors) {
+                let group = @@mut TCB(child,
+                                      child_arc,
+                                      ancestors,
+                                      is_main,
+                                      notifier);
+                unsafe {
+                    local_set(OldHandle(child), taskgroup_key!(), group);
+                }
+
+                // Run the child's body.
+                f();
+
+                // TLS cleanup code will exit the taskgroup.
+            }
+
+            // Run the box annihilator.
+            // FIXME #4428: Crashy.
+            // unsafe { cleanup::annihilate(); }
+        };
+        return result;
+
+        // 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: *rust_task, 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, true) // member
+                };
+            if result {
+                // Unwinding function in case any ancestral enlisting fails
+                let bail: @fn(TaskGroupInner) = |tg| {
+                    leave_taskgroup(tg, child, false)
+                };
+                // Attempt to join every ancestor group.
+                result =
+                    each_ancestor(ancestors, Some(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, 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
+        }
+    }
+
+    fn new_task_in_sched(opts: SchedOpts) -> *rust_task {
+        if opts.foreign_stack_size != None {
+            fail!("foreign_stack_size scheduler option unimplemented");
+        }
+
+        let num_threads = match opts.mode {
+            DefaultScheduler
+            | CurrentScheduler
+            | ExistingScheduler(*)
+            | PlatformThread => 0u, /* Won't be used */
+            SingleThreaded => 1u,
+            ThreadPerCore => unsafe { rt::rust_num_threads() },
+            ThreadPerTask => {
+                fail!("ThreadPerTask scheduling mode unimplemented")
+            }
+            ManualThreads(threads) => {
+                if threads == 0u {
+                    fail!("can not create a scheduler with no threads");
+                }
+                threads
+            }
+        };
+
+        unsafe {
+            let sched_id = match opts.mode {
+                CurrentScheduler => rt::rust_get_sched_id(),
+                ExistingScheduler(SchedulerHandle(id)) => id,
+                PlatformThread => rt::rust_osmain_sched_id(),
+                _ => rt::rust_new_sched(num_threads)
+            };
+            rt::rust_new_task_in_sched(sched_id)
+        }
+    }
+}
+
+#[test]
+fn test_spawn_raw_simple() {
+    let (po, ch) = stream();
+    do spawn_raw(default_task_opts()) {
+        ch.send(());
+    }
+    po.recv();
+}
+
+#[test]
+#[ignore(cfg(windows))]
+fn test_spawn_raw_unsupervise() {
+    let opts = task::TaskOpts {
+        linked: false,
+        notify_chan: None,
+        .. default_task_opts()
+    };
+    do spawn_raw(opts) {
+        fail!();
+    }
+}
+
+#[test]
+#[ignore(cfg(windows))]
+fn test_spawn_raw_notify_success() {
+    let (notify_po, notify_ch) = comm::stream();
+
+    let opts = task::TaskOpts {
+        notify_chan: Some(notify_ch),
+        .. default_task_opts()
+    };
+    do spawn_raw(opts) {
+    }
+    assert_eq!(notify_po.recv(), Success);
+}
+
+#[test]
+#[ignore(cfg(windows))]
+fn test_spawn_raw_notify_failure() {
+    // New bindings for these
+    let (notify_po, notify_ch) = comm::stream();
+
+    let opts = task::TaskOpts {
+        linked: false,
+        notify_chan: Some(notify_ch),
+        .. default_task_opts()
+    };
+    do spawn_raw(opts) {
+        fail!();
+    }
+    assert_eq!(notify_po.recv(), Failure);
+}