26
26
import result:: result;
27
27
import dvec:: extensions;
28
28
import dvec_iter:: extensions;
29
+ import arc:: methods;
29
30
30
31
export task;
31
32
export task_result;
@@ -563,7 +564,11 @@ unsafe fn unkillable(f: fn()) {
563
564
}
564
565
565
566
566
- /* Internal */
567
+ /****************************************************************************
568
+ * Internal
569
+ ****************************************************************************/
570
+
571
+ /* spawning */
567
572
568
573
type sched_id = int ;
569
574
type task_id = int ;
@@ -573,42 +578,185 @@ type task_id = int;
573
578
type rust_task = libc:: c_void ;
574
579
type rust_closure = libc:: c_void ;
575
580
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
+ }
577
610
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
+ }
588
629
}
589
- } ;
630
+ }
631
+ }
590
632
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
+ }
594
643
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
+ }
602
670
} ;
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
+ } ;
604
697
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) ;
608
729
}
730
+ }
609
731
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
+ }
612
760
}
613
761
614
762
fn new_task_in_new_sched ( opts : sched_opts ) -> * rust_task {
@@ -640,7 +788,6 @@ fn spawn_raw(opts: task_opts, +f: fn~()) {
640
788
} ;
641
789
rustrt:: rust_new_task_in_sched ( sched_id)
642
790
}
643
-
644
791
}
645
792
646
793
/****************************************************************************
@@ -760,7 +907,7 @@ unsafe fn local_get<T>(task: *rust_task,
760
907
local_get_helper ( task, key, false )
761
908
}
762
909
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 ) {
764
911
let map = get_task_local_map ( task) ;
765
912
// Store key+data as *voids. Data is invisibly referenced once; key isn't.
766
913
let keyval = key_to_key_value ( key) ;
@@ -822,7 +969,7 @@ unsafe fn local_data_get<T>(key: local_data_key<T>) -> option<@T> {
822
969
* Store a value in task-local data. If this key already has a value,
823
970
* that value is overwritten (and its destructor is run).
824
971
*/
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 ) {
826
973
local_set ( rustrt:: rust_get_task ( ) , key, data)
827
974
}
828
975
/**
@@ -853,11 +1000,12 @@ extern mod rustrt {
853
1000
854
1001
fn start_task ( task : * rust_task , closure : * rust_closure ) ;
855
1002
856
- fn rust_task_is_unwinding ( rt : * rust_task ) -> bool ;
1003
+ fn rust_task_is_unwinding ( task : * rust_task ) -> bool ;
857
1004
fn unsupervise ( ) ;
858
1005
fn rust_osmain_sched_id ( ) -> sched_id ;
859
1006
fn rust_task_inhibit_kill ( ) ;
860
1007
fn rust_task_allow_kill ( ) ;
1008
+ fn rust_task_kill_other ( task : * rust_task ) ;
861
1009
862
1010
#[ rust_stack]
863
1011
fn rust_get_task_local_data ( task : * rust_task ) -> * libc:: c_void ;
@@ -1232,7 +1380,7 @@ fn test_unkillable() {
1232
1380
let ch = po. chan ( ) ;
1233
1381
1234
1382
// We want to do this after failing
1235
- do spawn {
1383
+ do spawn_raw ( { supervise : false with default_task_opts ( ) } ) {
1236
1384
for iter:: repeat( 10 u) { yield ( ) }
1237
1385
ch. send ( ( ) ) ;
1238
1386
}
@@ -1269,7 +1417,7 @@ fn test_unkillable_nested() {
1269
1417
let ch = po. chan ( ) ;
1270
1418
1271
1419
// We want to do this after failing
1272
- do spawn {
1420
+ do spawn_raw ( { supervise : false with default_task_opts ( ) } ) {
1273
1421
for iter:: repeat( 10 u) { yield ( ) }
1274
1422
ch. send ( ( ) ) ;
1275
1423
}
0 commit comments