Skip to content

Commit acb8692

Browse files
committed
Revert linked failure
This reverts commit 5d6d3d0.
1 parent 08c40c5 commit acb8692

File tree

3 files changed

+67
-188
lines changed

3 files changed

+67
-188
lines changed

src/libcore/task.rs

Lines changed: 35 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import result::result;
2727
import dvec::extensions;
2828
import dvec_iter::extensions;
29-
import arc::methods;
3029

3130
export task;
3231
export task_result;
@@ -564,11 +563,7 @@ unsafe fn unkillable(f: fn()) {
564563
}
565564

566565

567-
/****************************************************************************
568-
* Internal
569-
****************************************************************************/
570-
571-
/* spawning */
566+
/* Internal */
572567

573568
type sched_id = int;
574569
type task_id = int;
@@ -578,185 +573,42 @@ type task_id = int;
578573
type rust_task = libc::c_void;
579574
type rust_closure = libc::c_void;
580575

581-
/* linked failure */
582-
583-
type taskgroup_arc = arc::exclusive<option<dvec::dvec<option<*rust_task>>>>;
584-
585-
class taskgroup {
586-
// FIXME (#2816): Change dvec to an O(1) data structure (and change 'me'
587-
// to a node-handle or somesuch when so done (or remove the field entirely
588-
// if keyed by *rust_task)).
589-
let tasks: taskgroup_arc; // 'none' means the group already failed.
590-
let me: *rust_task;
591-
let my_pos: uint;
592-
// let parent_group: taskgroup_arc; // TODO(bblum)
593-
// TODO XXX bblum: add a list of empty slots to get runtime back
594-
let mut failed: bool;
595-
new(-tasks: taskgroup_arc, me: *rust_task, my_pos: uint) {
596-
self.tasks = tasks; self.me = me; self.my_pos = my_pos;
597-
self.failed = true; // This will get un-set on successful exit.
598-
}
599-
// Runs on task exit.
600-
drop {
601-
if self.failed {
602-
// Take everybody down with us.
603-
kill_taskgroup(self.tasks, self.me, self.my_pos);
604-
} else {
605-
// Remove ourselves from the group.
606-
leave_taskgroup(self.tasks, self.me, self.my_pos);
607-
}
608-
}
609-
}
610-
611-
fn taskgroup_key(+_group: @taskgroup) { } // For TLS
612-
613-
fn enlist_in_taskgroup(group_arc: taskgroup_arc,
614-
me: *rust_task) -> option<uint> {
615-
do group_arc.with |_c, state| {
616-
// If 'none', the group was failing. Can't enlist.
617-
do state.map |tasks| {
618-
// Try to find an empty slot.
619-
alt tasks.position(|x| x == none) {
620-
some(empty_index) {
621-
tasks.set_elt(empty_index, some(me));
622-
empty_index
623-
}
624-
none {
625-
tasks.push(some(me));
626-
tasks.len() - 1
627-
}
628-
}
629-
}
630-
}
631-
}
632-
633-
// NB: Runs in destructor/post-exit context. Can't 'fail'.
634-
fn leave_taskgroup(group_arc: taskgroup_arc, me: *rust_task, index: uint) {
635-
do group_arc.with |_c, state| {
636-
// If 'none', already failing and we've already gotten a kill signal.
637-
do state.map |tasks| {
638-
assert tasks[index] == some(me);
639-
tasks.set_elt(index, none);
640-
};
641-
};
642-
}
643-
644-
// NB: Runs in destructor/post-exit context. Can't 'fail'.
645-
fn kill_taskgroup(group_arc: taskgroup_arc, me: *rust_task, index: uint) {
646-
// NB: We could do the killing iteration outside of the group arc, by
647-
// having "let mut newstate" here, swapping inside, and iterating after.
648-
// But that would let other exiting tasks fall-through and exit while we
649-
// were trying to kill them, causing potential use-after-free. A task's
650-
// presence in the arc guarantees it's alive only while we hold the lock,
651-
// so if we're failing, all concurrently exiting tasks must wait for us.
652-
// To do it differently, we'd have to use the runtime's task refcounting.
653-
do group_arc.with |_c, state| {
654-
let mut newstate = none;
655-
*state <-> newstate;
656-
// Might already be none, if somebody is failing simultaneously.
657-
// That's ok; only one task needs to do the dirty work. (Might also
658-
// see 'none' if somebody already failed and we got a kill signal.)
659-
do newstate.map |tasks| {
660-
// First remove ourself (killing ourself won't do much good). This
661-
// is duplicated here to avoid having to lock twice.
662-
assert tasks[index] == some(me);
663-
tasks.set_elt(index, none);
664-
// Now send takedown signal.
665-
for tasks.each |entry| {
666-
do entry.map |task| {
667-
rustrt::rust_task_kill_other(task);
668-
};
669-
}
670-
};
671-
};
672-
}
673-
674-
fn share_parent_taskgroup() -> taskgroup_arc {
675-
let me = rustrt::rust_get_task();
676-
alt unsafe { local_get(me, taskgroup_key) } {
677-
some(group) {
678-
group.tasks.clone()
679-
}
680-
none {
681-
/* Main task, doing first spawn ever. */
682-
let tasks = arc::exclusive(some(dvec::from_elem(some(me))));
683-
let group = @taskgroup(tasks.clone(), me, 0);
684-
unsafe { local_set(me, taskgroup_key, group); }
685-
tasks
686-
}
687-
}
688-
}
689-
690576
fn spawn_raw(opts: task_opts, +f: fn~()) {
691-
// Decide whether the child needs to be in a new linked failure group.
692-
let child_tg: taskgroup_arc = if opts.supervise {
693-
share_parent_taskgroup()
577+
578+
let mut f = if opts.supervise {
579+
f
694580
} else {
695-
arc::exclusive(some(dvec::from_elem(none)))
581+
// FIXME (#1868, #1789): The runtime supervision API is weird here
582+
// because it was designed to let the child unsupervise itself,
583+
// when what we actually want is for parents to unsupervise new
584+
// children.
585+
fn~() {
586+
rustrt::unsupervise();
587+
f();
588+
}
696589
};
697590

698591
unsafe {
699-
let child_data_ptr = ~mut some((child_tg, f));
700-
// Being killed with the unsafe task/closure pointers would leak them.
701-
do unkillable {
702-
// Agh. Get move-mode items into the closure. FIXME (#2829)
703-
let mut child_data = none;
704-
*child_data_ptr <-> child_data;
705-
let (child_tg, f) = option::unwrap(child_data);
706-
// Create child task.
707-
let new_task = alt opts.sched {
708-
none { rustrt::new_task() }
709-
some(sched_opts) { new_task_in_new_sched(sched_opts) }
710-
};
711-
assert !new_task.is_null();
712-
// Getting killed after here would leak the task.
713-
714-
let child_wrapper =
715-
make_child_wrapper(new_task, child_tg, opts.supervise, f);
716-
let fptr = ptr::addr_of(child_wrapper);
717-
let closure: *rust_closure = unsafe::reinterpret_cast(fptr);
718-
719-
do option::iter(opts.notify_chan) |c| {
720-
// FIXME (#1087): Would like to do notification in Rust
721-
rustrt::rust_task_config_notify(new_task, c);
722-
}
592+
let fptr = ptr::addr_of(f);
593+
let closure: *rust_closure = unsafe::reinterpret_cast(fptr);
723594

724-
// Getting killed between these two calls would free the child's
725-
// closure. (Reordering them wouldn't help - then getting killed
726-
// between them would leak.)
727-
rustrt::start_task(new_task, closure);
728-
unsafe::forget(child_wrapper);
729-
}
730-
}
595+
let new_task = alt opts.sched {
596+
none {
597+
rustrt::new_task()
598+
}
599+
some(sched_opts) {
600+
new_task_in_new_sched(sched_opts)
601+
}
602+
};
603+
assert !new_task.is_null();
731604

732-
fn make_child_wrapper(child_task: *rust_task, -child_tg: taskgroup_arc,
733-
supervise: bool, -f: fn~()) -> fn~() {
734-
let child_tg_ptr = ~mut some(child_tg);
735-
fn~() {
736-
// Agh. Get move-mode items into the closure. FIXME (#2829)
737-
let mut child_tg_opt = none;
738-
*child_tg_ptr <-> child_tg_opt;
739-
let child_tg = option::unwrap(child_tg_opt);
740-
// Child task runs this code.
741-
if !supervise {
742-
// FIXME (#1868, #1789) take this out later
743-
rustrt::unsupervise();
744-
}
745-
// Set up membership in taskgroup. If this returns none, the
746-
// parent was already failing, so don't bother doing anything.
747-
alt enlist_in_taskgroup(child_tg, child_task) {
748-
some(my_index) {
749-
let group = @taskgroup(child_tg, child_task, my_index);
750-
unsafe { local_set(child_task, taskgroup_key, group); }
751-
// Run the child's body.
752-
f();
753-
// Report successful exit. (TLS cleanup code will tear
754-
// down the group.)
755-
group.failed = false;
756-
}
757-
none { }
758-
}
605+
do option::iter(opts.notify_chan) |c| {
606+
// FIXME (#1087): Would like to do notification in Rust
607+
rustrt::rust_task_config_notify(new_task, c);
759608
}
609+
610+
rustrt::start_task(new_task, closure);
611+
unsafe::forget(f);
760612
}
761613

762614
fn new_task_in_new_sched(opts: sched_opts) -> *rust_task {
@@ -788,6 +640,7 @@ fn spawn_raw(opts: task_opts, +f: fn~()) {
788640
};
789641
rustrt::rust_new_task_in_sched(sched_id)
790642
}
643+
791644
}
792645

793646
/****************************************************************************
@@ -907,7 +760,7 @@ unsafe fn local_get<T>(task: *rust_task,
907760
local_get_helper(task, key, false)
908761
}
909762

910-
unsafe fn local_set<T>(task: *rust_task, key: local_data_key<T>, +data: @T) {
763+
unsafe fn local_set<T>(task: *rust_task, key: local_data_key<T>, -data: @T) {
911764
let map = get_task_local_map(task);
912765
// Store key+data as *voids. Data is invisibly referenced once; key isn't.
913766
let keyval = key_to_key_value(key);
@@ -969,7 +822,7 @@ unsafe fn local_data_get<T>(key: local_data_key<T>) -> option<@T> {
969822
* Store a value in task-local data. If this key already has a value,
970823
* that value is overwritten (and its destructor is run).
971824
*/
972-
unsafe fn local_data_set<T>(key: local_data_key<T>, +data: @T) {
825+
unsafe fn local_data_set<T>(key: local_data_key<T>, -data: @T) {
973826
local_set(rustrt::rust_get_task(), key, data)
974827
}
975828
/**
@@ -1000,12 +853,11 @@ extern mod rustrt {
1000853

1001854
fn start_task(task: *rust_task, closure: *rust_closure);
1002855

1003-
fn rust_task_is_unwinding(task: *rust_task) -> bool;
856+
fn rust_task_is_unwinding(rt: *rust_task) -> bool;
1004857
fn unsupervise();
1005858
fn rust_osmain_sched_id() -> sched_id;
1006859
fn rust_task_inhibit_kill();
1007860
fn rust_task_allow_kill();
1008-
fn rust_task_kill_other(task: *rust_task);
1009861

1010862
#[rust_stack]
1011863
fn rust_get_task_local_data(task: *rust_task) -> *libc::c_void;
@@ -1380,7 +1232,7 @@ fn test_unkillable() {
13801232
let ch = po.chan();
13811233

13821234
// We want to do this after failing
1383-
do spawn_raw({ supervise: false with default_task_opts() }) {
1235+
do spawn {
13841236
for iter::repeat(10u) { yield() }
13851237
ch.send(());
13861238
}
@@ -1417,7 +1269,7 @@ fn test_unkillable_nested() {
14171269
let ch = po.chan();
14181270

14191271
// We want to do this after failing
1420-
do spawn_raw({ supervise: false with default_task_opts() }) {
1272+
do spawn {
14211273
for iter::repeat(10u) { yield() }
14221274
ch.send(());
14231275
}

src/rt/rust_task.cpp

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
#include "rust_env.h"
1111
#include "rust_port.h"
1212

13-
// TODO(bblum): get rid of supervisors
14-
1513
// Tasks
1614
rust_task::rust_task(rust_sched_loop *sched_loop, rust_task_state state,
1715
rust_task *spawner, const char *name,
@@ -148,9 +146,13 @@ cleanup_task(cleanup_args *args) {
148146

149147
task->notify(!threw_exception);
150148

151-
#ifdef __WIN32__
152-
assert(!threw_exception && "No exception-handling yet on windows builds");
149+
if (threw_exception) {
150+
#ifndef __WIN32__
151+
task->conclude_failure();
152+
#else
153+
assert(false && "Shouldn't happen");
153154
#endif
155+
}
154156
}
155157

156158
extern "C" CDECL void upcall_exchange_free(void *ptr);
@@ -260,7 +262,10 @@ void
260262
rust_task::kill() {
261263
scoped_lock with(kill_lock);
262264

263-
// XXX: bblum: kill/kill race
265+
if (dead()) {
266+
// Task is already dead, can't kill what's already dead.
267+
fail_parent();
268+
}
264269

265270
// Note the distinction here: kill() is when you're in an upcall
266271
// from task A and want to force-fail task B, you do B->kill().
@@ -309,11 +314,31 @@ rust_task::begin_failure(char const *expr, char const *file, size_t line) {
309314
throw this;
310315
#else
311316
die();
317+
conclude_failure();
312318
// FIXME (#908): Need unwinding on windows. This will end up aborting
313319
sched_loop->fail();
314320
#endif
315321
}
316322

323+
void
324+
rust_task::conclude_failure() {
325+
fail_parent();
326+
}
327+
328+
void
329+
rust_task::fail_parent() {
330+
scoped_lock with(supervisor_lock);
331+
if (supervisor) {
332+
DLOG(sched_loop, task,
333+
"task %s @0x%" PRIxPTR
334+
" propagating failure to supervisor %s @0x%" PRIxPTR,
335+
name, this, supervisor->name, supervisor);
336+
supervisor->kill();
337+
}
338+
if (NULL == supervisor && propagate_failure)
339+
sched_loop->fail();
340+
}
341+
317342
void
318343
rust_task::unsupervise()
319344
{

src/rt/rust_task.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ rust_task : public kernel_owned<rust_task>
275275
// Fail self, assuming caller-on-stack is this task.
276276
void fail();
277277
void fail(char const *expr, char const *file, size_t line);
278+
void conclude_failure();
279+
void fail_parent();
278280

279281
// Disconnect from our supervisor.
280282
void unsupervise();

0 commit comments

Comments
 (0)