diff options
| author | Patrick Walton <pcwalton@mimiga.net> | 2013-05-17 10:45:09 -0700 |
|---|---|---|
| committer | Patrick Walton <pcwalton@mimiga.net> | 2013-05-22 21:57:05 -0700 |
| commit | 0c820d4123c754522b0655e9e74f692c55685bfa (patch) | |
| tree | 7dbb86c30b451217b4e8f75173043744fe3255ff /src/libstd/task | |
| parent | 565942b145efbf6c1d1f66db46423d721b55d32c (diff) | |
| download | rust-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.rs | 235 | ||||
| -rw-r--r-- | src/libstd/task/mod.rs | 1189 | ||||
| -rw-r--r-- | src/libstd/task/rt.rs | 71 | ||||
| -rw-r--r-- | src/libstd/task/spawn.rs | 791 |
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); +} |
