From 0c820d4123c754522b0655e9e74f692c55685bfa Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 17 May 2013 10:45:09 -0700 Subject: 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. --- src/libcore/task/local_data_priv.rs | 235 ------- src/libcore/task/mod.rs | 1189 ----------------------------------- src/libcore/task/rt.rs | 71 --- src/libcore/task/spawn.rs | 791 ----------------------- 4 files changed, 2286 deletions(-) delete mode 100644 src/libcore/task/local_data_priv.rs delete mode 100644 src/libcore/task/mod.rs delete mode 100644 src/libcore/task/rt.rs delete mode 100644 src/libcore/task/spawn.rs (limited to 'src/libcore/task') diff --git a/src/libcore/task/local_data_priv.rs b/src/libcore/task/local_data_priv.rs deleted file mode 100644 index 2f97eaacf4b..00000000000 --- a/src/libcore/task/local_data_priv.rs +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2012 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -#[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::(); - NewHandle(&mut (*task).storage) - } - } - } - } -} - -pub trait LocalData { } -impl 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]; - -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::]>(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::]>(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(key: LocalDataKey) -> *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( - map: TaskLocalMap, key: LocalDataKey) - -> 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( - handle: Handle, key: LocalDataKey, - 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( - handle: Handle, - key: LocalDataKey) -> Option<@T> { - - local_get_helper(handle, key, true) -} - -pub unsafe fn local_get( - handle: Handle, - key: LocalDataKey) -> Option<@T> { - - local_get_helper(handle, key, false) -} - -pub unsafe fn local_set( - handle: Handle, key: LocalDataKey, 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( - handle: Handle, key: LocalDataKey, - 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/libcore/task/mod.rs b/src/libcore/task/mod.rs deleted file mode 100644 index 490a69248ee..00000000000 --- a/src/libcore/task/mod.rs +++ /dev/null @@ -1,1189 +0,0 @@ -// Copyright 2012 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -/*! - * Task management. - * - * An executing Rust program consists of a tree of tasks, each with their own - * stack, and sole ownership of their allocated heap data. Tasks communicate - * with each other using ports and channels. - * - * When a task fails, that failure will propagate to its parent (the task - * that spawned it) and the parent will fail as well. The reverse is not - * true: when a parent task fails its children will continue executing. When - * the root (main) task fails, all tasks fail, and then so does the entire - * process. - * - * Tasks may execute in parallel and are scheduled automatically by the - * runtime. - * - * # Example - * - * ~~~ - * do spawn { - * log(error, "Hello, World!"); - * } - * ~~~ - */ - -use cell::Cell; -use cmp::Eq; -use result::Result; -use comm::{stream, Chan, GenericChan, GenericPort, Port}; -use prelude::*; -use result; -use task::rt::{task_id, sched_id}; -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, -} - -/** - * 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>, - 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, - 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)) { - // FIXME (#3725): Once linked failure and notification are - // handled in the library, I can imagine implementing this by just - // registering an arbitrary number of task::on_exit handlers and - // sending out messages. - - if self.opts.notify_chan.is_some() { - fail!("Can't set multiple future_results for one task!"); - } - - // Construct the future and give it to the caller. - let (notify_pipe_po, notify_pipe_ch) = stream::(); - - blk(notify_pipe_po); - - // Reconfigure self to use a notify channel. - 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(&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(&mut self, f: ~fn() -> T) -> Result { - let (po, ch) = stream::(); - 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(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(f: ~fn() -> T) -> Result { - /*! - * Execute a function in another task and return either the return value - * of the function or result::err. - * - * This is equivalent to task().supervised().try. - */ - - 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:: |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(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(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(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> -} - -#[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, ch: &Chan) { - let mut val = 20; - while val > 0 { - val = po.recv(); - ch.send(val - 1); - } - } - - let (setup_po, setup_ch) = stream(); - let (parent_po, parent_ch) = stream(); - do spawn { - let (child_po, child_ch) = stream(); - setup_ch.send(child_ch); - pingpong(&child_po, &parent_ch); - }; - - let child_ch = setup_po.recv(); - child_ch.send(20); - pingpong(&parent_po, &child_ch); - testrt::rust_dbg_lock_lock(lock); - testrt::rust_dbg_lock_signal(lock); - testrt::rust_dbg_lock_unlock(lock); - fin_po.recv(); - testrt::rust_dbg_lock_destroy(lock); - } - } -} - -#[cfg(test)] -fn avoid_copying_the_body(spawnfn: &fn(v: ~fn())) { - let (p, ch) = stream::(); - - let x = ~1; - let x_in_parent = ptr::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/libcore/task/rt.rs b/src/libcore/task/rt.rs deleted file mode 100644 index 760812252bc..00000000000 --- a/src/libcore/task/rt.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2012 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -/*! - -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/libcore/task/spawn.rs b/src/libcore/task/spawn.rs deleted file mode 100644 index 81e5af5caab..00000000000 --- a/src/libcore/task/spawn.rs +++ /dev/null @@ -1,791 +0,0 @@ -// Copyright 2012 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -/*!************************************************************************** - * 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>; - -type TaskGroupInner<'self> = &'self mut Option; - -// 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, - // Recursive rest of the list. - ancestors: AncestorList, -} - -struct AncestorList(Option>); - -// Accessors for taskgroup arcs and ancestor arcs that wrap the unsafety. -#[inline(always)] -fn access_group(x: &TaskGroupArc, blk: &fn(TaskGroupInner) -> U) -> U { - x.with(blk) -} - -#[inline(always)] -fn access_ancestors(x: &Exclusive, - 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: - // 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, 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(parent_group: &mut Option, - 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, -} - -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) -> 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, - 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) -> 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::(); - 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>, - 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); -} -- cgit 1.4.1-3-g733a5