Skip to content

Commit 5d6d3d0

Browse files
committed
Linked failure in task.rs instead of rust_task.cpp (#1868, #1189)
1 parent 152f2ea commit 5d6d3d0

File tree

3 files changed

+188
-67
lines changed

3 files changed

+188
-67
lines changed

src/libcore/task.rs

+183-35
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import result::result;
2727
import dvec::extensions;
2828
import dvec_iter::extensions;
29+
import arc::methods;
2930

3031
export task;
3132
export task_result;
@@ -563,7 +564,11 @@ unsafe fn unkillable(f: fn()) {
563564
}
564565

565566

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

568573
type sched_id = int;
569574
type task_id = int;
@@ -573,42 +578,185 @@ type task_id = int;
573578
type rust_task = libc::c_void;
574579
type rust_closure = libc::c_void;
575580

576-
fn spawn_raw(opts: task_opts, +f: fn~()) {
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+
}
577610

578-
let mut f = if opts.supervise {
579-
f
580-
} else {
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();
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+
}
588629
}
589-
};
630+
}
631+
}
590632

591-
unsafe {
592-
let fptr = ptr::addr_of(f);
593-
let closure: *rust_closure = unsafe::reinterpret_cast(fptr);
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+
}
594643

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-
}
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+
}
602670
};
603-
assert !new_task.is_null();
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+
690+
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()
694+
} else {
695+
arc::exclusive(some(dvec::from_elem(none)))
696+
};
604697

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);
698+
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+
}
723+
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);
608729
}
730+
}
609731

610-
rustrt::start_task(new_task, closure);
611-
unsafe::forget(f);
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+
}
759+
}
612760
}
613761

614762
fn new_task_in_new_sched(opts: sched_opts) -> *rust_task {
@@ -640,7 +788,6 @@ fn spawn_raw(opts: task_opts, +f: fn~()) {
640788
};
641789
rustrt::rust_new_task_in_sched(sched_id)
642790
}
643-
644791
}
645792

646793
/****************************************************************************
@@ -760,7 +907,7 @@ unsafe fn local_get<T>(task: *rust_task,
760907
local_get_helper(task, key, false)
761908
}
762909

763-
unsafe fn local_set<T>(task: *rust_task, key: local_data_key<T>, -data: @T) {
910+
unsafe fn local_set<T>(task: *rust_task, key: local_data_key<T>, +data: @T) {
764911
let map = get_task_local_map(task);
765912
// Store key+data as *voids. Data is invisibly referenced once; key isn't.
766913
let keyval = key_to_key_value(key);
@@ -822,7 +969,7 @@ unsafe fn local_data_get<T>(key: local_data_key<T>) -> option<@T> {
822969
* Store a value in task-local data. If this key already has a value,
823970
* that value is overwritten (and its destructor is run).
824971
*/
825-
unsafe fn local_data_set<T>(key: local_data_key<T>, -data: @T) {
972+
unsafe fn local_data_set<T>(key: local_data_key<T>, +data: @T) {
826973
local_set(rustrt::rust_get_task(), key, data)
827974
}
828975
/**
@@ -853,11 +1000,12 @@ extern mod rustrt {
8531000

8541001
fn start_task(task: *rust_task, closure: *rust_closure);
8551002

856-
fn rust_task_is_unwinding(rt: *rust_task) -> bool;
1003+
fn rust_task_is_unwinding(task: *rust_task) -> bool;
8571004
fn unsupervise();
8581005
fn rust_osmain_sched_id() -> sched_id;
8591006
fn rust_task_inhibit_kill();
8601007
fn rust_task_allow_kill();
1008+
fn rust_task_kill_other(task: *rust_task);
8611009

8621010
#[rust_stack]
8631011
fn rust_get_task_local_data(task: *rust_task) -> *libc::c_void;
@@ -1232,7 +1380,7 @@ fn test_unkillable() {
12321380
let ch = po.chan();
12331381

12341382
// We want to do this after failing
1235-
do spawn {
1383+
do spawn_raw({ supervise: false with default_task_opts() }) {
12361384
for iter::repeat(10u) { yield() }
12371385
ch.send(());
12381386
}
@@ -1269,7 +1417,7 @@ fn test_unkillable_nested() {
12691417
let ch = po.chan();
12701418

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

src/rt/rust_task.cpp

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

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

147149
task->notify(!threw_exception);
148150

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

158156
extern "C" CDECL void upcall_exchange_free(void *ptr);
@@ -262,10 +260,7 @@ void
262260
rust_task::kill() {
263261
scoped_lock with(kill_lock);
264262

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

270265
// Note the distinction here: kill() is when you're in an upcall
271266
// from task A and want to force-fail task B, you do B->kill().
@@ -314,31 +309,11 @@ rust_task::begin_failure(char const *expr, char const *file, size_t line) {
314309
throw this;
315310
#else
316311
die();
317-
conclude_failure();
318312
// FIXME (#908): Need unwinding on windows. This will end up aborting
319313
sched_loop->fail();
320314
#endif
321315
}
322316

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-
342317
void
343318
rust_task::unsupervise()
344319
{

src/rt/rust_task.h

-2
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,6 @@ 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();
280278

281279
// Disconnect from our supervisor.
282280
void unsupervise();

0 commit comments

Comments
 (0)