2626import result:: result;
2727import dvec:: extensions;
2828import dvec_iter:: extensions;
29+ import arc:: methods;
2930
3031export task;
3132export task_result;
@@ -563,7 +564,11 @@ unsafe fn unkillable(f: fn()) {
563564}
564565
565566
566- /* Internal */
567+ /****************************************************************************
568+ * Internal
569+ ****************************************************************************/
570+
571+ /* spawning */
567572
568573type sched_id = int ;
569574type task_id = int ;
@@ -573,42 +578,185 @@ type task_id = int;
573578type rust_task = libc:: c_void ;
574579type 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( 10 u) { 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( 10 u) { yield ( ) }
12741422 ch. send ( ( ) ) ;
12751423 }
0 commit comments