From 200a2ded3245eb0a1ca7b265ce83adba16d75b97 Mon Sep 17 00:00:00 2001 From: Ben Blum Date: Thu, 12 Jul 2012 02:42:56 -0400 Subject: [PATCH] Fix linked failure with root taskgroup to kill the runtime too. --- src/libcore/task.rs | 56 ++++++++++++++++++++++++-------------- src/rt/rust_builtin.cpp | 5 ++++ src/rt/rust_sched_loop.cpp | 4 +-- src/rt/rust_task.cpp | 12 +++++++- src/rt/rust_task.h | 4 +++ src/rt/rustrt.def.in | 1 + 6 files changed, 59 insertions(+), 23 deletions(-) diff --git a/src/libcore/task.rs b/src/libcore/task.rs index 5f3cf1e5c26..cef1102f09f 100644 --- a/src/libcore/task.rs +++ b/src/libcore/task.rs @@ -591,16 +591,21 @@ class taskgroup { let my_pos: uint; // let parent_group: taskgroup_arc; // TODO(bblum) // TODO XXX bblum: add a list of empty slots to get runtime back - let mut failed: bool; - new(-tasks: taskgroup_arc, me: *rust_task, my_pos: uint) { - self.tasks = tasks; self.me = me; self.my_pos = my_pos; - self.failed = true; // This will get un-set on successful exit. + // Indicates whether this is the main (root) taskgroup. If so, failure + // here should take down the entire runtime. + let is_main: bool; + new(-tasks: taskgroup_arc, me: *rust_task, my_pos: uint, is_main: bool) { + self.tasks = tasks; + self.me = me; + self.my_pos = my_pos; + self.is_main = is_main; } // Runs on task exit. drop { - if self.failed { + // If we are failing, the whole taskgroup needs to die. + if rustrt::rust_task_is_unwinding(self.me) { // Take everybody down with us. - kill_taskgroup(self.tasks, self.me, self.my_pos); + kill_taskgroup(self.tasks, self.me, self.my_pos, self.is_main); } else { // Remove ourselves from the group. leave_taskgroup(self.tasks, self.me, self.my_pos); @@ -642,7 +647,8 @@ fn leave_taskgroup(group_arc: taskgroup_arc, me: *rust_task, index: uint) { } // NB: Runs in destructor/post-exit context. Can't 'fail'. -fn kill_taskgroup(group_arc: taskgroup_arc, me: *rust_task, index: uint) { +fn kill_taskgroup(group_arc: taskgroup_arc, me: *rust_task, index: uint, + is_main: bool) { // 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 @@ -667,32 +673,40 @@ fn kill_taskgroup(group_arc: taskgroup_arc, me: *rust_task, index: uint) { rustrt::rust_task_kill_other(task); }; } + // Only one task should ever do this. + if is_main { + rustrt::rust_task_kill_all(me); + } }; + // (note: multiple tasks may reach this point) }; } -fn share_parent_taskgroup() -> taskgroup_arc { +fn share_parent_taskgroup() -> (taskgroup_arc, bool) { let me = rustrt::rust_get_task(); alt unsafe { local_get(me, taskgroup_key) } { some(group) { - group.tasks.clone() + // Clone the shared state for the child; propagate main-ness. + (group.tasks.clone(), group.is_main) } none { - /* Main task, doing first spawn ever. */ + // Main task, doing first spawn ever. let tasks = arc::exclusive(some(dvec::from_elem(some(me)))); - let group = @taskgroup(tasks.clone(), me, 0); + let group = @taskgroup(tasks.clone(), me, 0, true); unsafe { local_set(me, taskgroup_key, group); } - tasks + // Tell child task it's also in the main group. + (tasks, true) } } } fn spawn_raw(opts: task_opts, +f: fn~()) { // Decide whether the child needs to be in a new linked failure group. - let child_tg: taskgroup_arc = if opts.supervise { + let (child_tg, is_main) = if opts.supervise { share_parent_taskgroup() } else { - arc::exclusive(some(dvec::from_elem(none))) + // Detached from the parent group; create a new (non-main) one. + (arc::exclusive(some(dvec::from_elem(none))), false) }; unsafe { @@ -712,7 +726,8 @@ fn spawn_raw(opts: task_opts, +f: fn~()) { // Getting killed after here would leak the task. let child_wrapper = - make_child_wrapper(new_task, child_tg, opts.supervise, f); + make_child_wrapper(new_task, child_tg, + opts.supervise, is_main, f); let fptr = ptr::addr_of(child_wrapper); let closure: *rust_closure = unsafe::reinterpret_cast(fptr); @@ -730,7 +745,8 @@ fn spawn_raw(opts: task_opts, +f: fn~()) { } fn make_child_wrapper(child_task: *rust_task, -child_tg: taskgroup_arc, - supervise: bool, -f: fn~()) -> fn~() { + supervise: bool, is_main: bool, + -f: fn~()) -> fn~() { let child_tg_ptr = ~mut some(child_tg); fn~() { // Agh. Get move-mode items into the closure. FIXME (#2829) @@ -746,13 +762,12 @@ fn spawn_raw(opts: task_opts, +f: fn~()) { // parent was already failing, so don't bother doing anything. alt enlist_in_taskgroup(child_tg, child_task) { some(my_index) { - let group = @taskgroup(child_tg, child_task, my_index); + let group = + @taskgroup(child_tg, child_task, my_index, is_main); unsafe { local_set(child_task, taskgroup_key, group); } // Run the child's body. f(); - // Report successful exit. (TLS cleanup code will tear - // down the group.) - group.failed = false; + // TLS cleanup code will exit the taskgroup. } none { } } @@ -1006,6 +1021,7 @@ extern mod rustrt { fn rust_task_inhibit_kill(); fn rust_task_allow_kill(); 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; diff --git a/src/rt/rust_builtin.cpp b/src/rt/rust_builtin.cpp index 55f1f8bf17e..f648ed8d78c 100644 --- a/src/rt/rust_builtin.cpp +++ b/src/rt/rust_builtin.cpp @@ -863,6 +863,11 @@ rust_task_kill_other(rust_task *task) { /* Used for linked failure */ task->kill(); } +extern "C" void +rust_task_kill_all(rust_task *task) { + task->fail_sched_loop(); +} + extern "C" rust_cond_lock* rust_create_cond_lock() { return new rust_cond_lock(); diff --git a/src/rt/rust_sched_loop.cpp b/src/rt/rust_sched_loop.cpp index 4aed9a5e061..54ebccfe8c3 100644 --- a/src/rt/rust_sched_loop.cpp +++ b/src/rt/rust_sched_loop.cpp @@ -260,8 +260,8 @@ rust_task * rust_sched_loop::create_task(rust_task *spawner, const char *name) { rust_task *task = new (this->kernel, "rust_task") - rust_task (this, task_state_newborn, - spawner, name, kernel->env->min_stack_size); + rust_task(this, task_state_newborn, + spawner, name, kernel->env->min_stack_size); DLOG(this, task, "created task: " PTR ", spawner: %s, name: %s", task, spawner ? spawner->name : "null", name); diff --git a/src/rt/rust_task.cpp b/src/rt/rust_task.cpp index 3d88c05b3ff..a6c9b791fda 100644 --- a/src/rt/rust_task.cpp +++ b/src/rt/rust_task.cpp @@ -129,6 +129,11 @@ cleanup_task(cleanup_args *args) { // assert(task->task_local_data != NULL); task->task_local_data_cleanup(task->task_local_data); task->task_local_data = NULL; + } else if (threw_exception) { + // Edge case: If main never spawns any tasks, but fails anyway, TLS + // won't be around to take down the kernel (task.rs:kill_taskgroup, + // rust_task_kill_all). Do it here instead. + task->fail_sched_loop(); } // FIXME (#2676): For performance we should do the annihilator @@ -282,6 +287,7 @@ rust_task::kill() { LOG(this, task, "preparing to unwind task: 0x%" PRIxPTR, this); } +// TODO(bblum): Move this to rust_builtin.cpp (cleanup) extern "C" CDECL bool rust_task_is_unwinding(rust_task *rt) { return rt->unwinding; @@ -315,10 +321,14 @@ rust_task::begin_failure(char const *expr, char const *file, size_t line) { #else die(); // FIXME (#908): Need unwinding on windows. This will end up aborting - sched_loop->fail(); + fail_sched_loop(); #endif } +void rust_task::fail_sched_loop() { + sched_loop->fail(); +} + void rust_task::unsupervise() { diff --git a/src/rt/rust_task.h b/src/rt/rust_task.h index 1d87a0ed56c..d562d151894 100644 --- a/src/rt/rust_task.h +++ b/src/rt/rust_task.h @@ -275,6 +275,10 @@ public: void fail(); void fail(char const *expr, char const *file, size_t line); + // Propagate failure to the entire rust runtime. + // TODO(bblum): maybe this can be done at rust-level? + void fail_sched_loop(); + // Disconnect from our supervisor. void unsupervise(); diff --git a/src/rt/rustrt.def.in b/src/rt/rustrt.def.in index 300d6bc79e8..a8256bba300 100644 --- a/src/rt/rustrt.def.in +++ b/src/rt/rustrt.def.in @@ -178,6 +178,7 @@ rust_port_task rust_task_inhibit_kill rust_task_allow_kill rust_task_kill_other +rust_task_kill_all rust_create_cond_lock rust_destroy_cond_lock rust_lock_cond_lock