about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/libcore/task.rs56
-rw-r--r--src/rt/rust_builtin.cpp5
-rw-r--r--src/rt/rust_sched_loop.cpp4
-rw-r--r--src/rt/rust_task.cpp12
-rw-r--r--src/rt/rust_task.h4
-rw-r--r--src/rt/rustrt.def.in1
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