rust/src/libstd/task/spawn.rs
2013-12-16 17:47:11 -08:00

233 lines
8.8 KiB
Rust

// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
/*!**************************************************************************
*
* WARNING: linked failure has been removed since this doc comment was written,
* but it was so pretty that I didn't want to remove it.
*
* 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 "taskgroup" 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), 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)];
use prelude::*;
use comm::Chan;
use rt::local::Local;
use rt::sched::{Scheduler, Shutdown, TaskFromFriend};
use rt::task::{Task, Sched};
use rt::thread::Thread;
use rt::{in_green_task_context, new_event_loop};
use task::{SingleThreaded, TaskOpts, TaskResult};
#[cfg(test)] use task::default_task_opts;
#[cfg(test)] use task;
pub fn spawn_raw(mut opts: TaskOpts, f: proc()) {
assert!(in_green_task_context());
let mut task = if opts.sched.mode != SingleThreaded {
if opts.watched {
Task::build_child(opts.stack_size, f)
} else {
Task::build_root(opts.stack_size, f)
}
} else {
unsafe {
// Creating a 1:1 task:thread ...
let sched: *mut Scheduler = Local::unsafe_borrow();
let sched_handle = (*sched).make_handle();
// Since this is a 1:1 scheduler we create a queue not in
// the stealee set. The run_anything flag is set false
// which will disable stealing.
let (worker, _stealer) = (*sched).work_queue.pool().deque();
// Create a new scheduler to hold the new task
let mut new_sched = ~Scheduler::new_special(new_event_loop(),
worker,
(*sched).work_queues.clone(),
(*sched).sleeper_list.clone(),
false,
Some(sched_handle));
let mut new_sched_handle = new_sched.make_handle();
// Allow the scheduler to exit when the pinned task exits
new_sched_handle.send(Shutdown);
// Pin the new task to the new scheduler
let new_task = if opts.watched {
Task::build_homed_child(opts.stack_size, f, Sched(new_sched_handle))
} else {
Task::build_homed_root(opts.stack_size, f, Sched(new_sched_handle))
};
// Create a task that will later be used to join with the new scheduler
// thread when it is ready to terminate
let (thread_port, thread_chan) = Chan::new();
let join_task = do Task::build_child(None) {
debug!("running join task");
let thread: Thread<()> = thread_port.recv();
thread.join();
};
// Put the scheduler into another thread
let orig_sched_handle = (*sched).make_handle();
let new_sched = new_sched;
let thread = do Thread::start {
let mut new_sched = new_sched;
let mut orig_sched_handle = orig_sched_handle;
let bootstrap_task = ~do Task::new_root(&mut new_sched.stack_pool, None) || {
debug!("boostrapping a 1:1 scheduler");
};
new_sched.bootstrap(bootstrap_task);
// Now tell the original scheduler to join with this thread
// by scheduling a thread-joining task on the original scheduler
orig_sched_handle.send(TaskFromFriend(join_task));
// NB: We can't simply send a message from here to another task
// because this code isn't running in a task and message passing doesn't
// work outside of tasks. Hence we're sending a scheduler message
// to execute a new task directly to a scheduler.
};
// Give the thread handle to the join task
thread_chan.send(thread);
// When this task is enqueued on the current scheduler it will then get
// forwarded to the scheduler to which it is pinned
new_task
}
};
if opts.notify_chan.is_some() {
let notify_chan = opts.notify_chan.take_unwrap();
let on_exit: proc(TaskResult) = proc(task_result) {
notify_chan.send(task_result)
};
task.death.on_exit = Some(on_exit);
}
task.name = opts.name.take();
debug!("spawn calling run_task");
Scheduler::run_task(task);
}
#[test]
fn test_spawn_raw_simple() {
let (po, ch) = Chan::new();
do spawn_raw(default_task_opts()) {
ch.send(());
}
po.recv();
}
#[test]
fn test_spawn_raw_unsupervise() {
let opts = task::TaskOpts {
watched: false,
notify_chan: None,
.. default_task_opts()
};
do spawn_raw(opts) {
fail!();
}
}
#[test]
fn test_spawn_raw_notify_success() {
let (notify_po, notify_ch) = Chan::new();
let opts = task::TaskOpts {
notify_chan: Some(notify_ch),
.. default_task_opts()
};
do spawn_raw(opts) {
}
assert!(notify_po.recv().is_ok());
}
#[test]
fn test_spawn_raw_notify_failure() {
// New bindings for these
let (notify_po, notify_ch) = Chan::new();
let opts = task::TaskOpts {
watched: false,
notify_chan: Some(notify_ch),
.. default_task_opts()
};
do spawn_raw(opts) {
fail!();
}
assert!(notify_po.recv().is_err());
}