@@ -23,6 +23,7 @@ spawn {||
23
23
" ] ;
24
24
25
25
import result:: result;
26
+ import dvec:: extensions;
26
27
27
28
export task;
28
29
export task_result;
@@ -53,6 +54,12 @@ export failing;
53
54
export get_task;
54
55
export unkillable;
55
56
57
+ export local_data_key;
58
+ export local_data_pop;
59
+ export local_data_get;
60
+ export local_data_set;
61
+ export local_data_modify;
62
+
56
63
/* Data types */
57
64
58
65
#[ doc = "A handle to a task" ]
@@ -573,6 +580,187 @@ fn spawn_raw(opts: task_opts, +f: fn~()) {
573
580
574
581
}
575
582
583
+ /****************************************************************************
584
+ * Task local data management
585
+ *
586
+ * Allows storing boxes with arbitrary types inside, to be accessed anywhere
587
+ * within a task, keyed by a pointer to a global finaliser function. Useful
588
+ * for task-spawning metadata (tracking linked failure state), dynamic
589
+ * variables, and interfacing with foreign code with bad callback interfaces.
590
+ *
591
+ * To use, declare a monomorphic global function at the type to store, and use
592
+ * it as the 'key' when accessing. See the 'tls' tests below for examples.
593
+ *
594
+ * Casting 'Arcane Sight' reveals an overwhelming aura of Transmutation magic.
595
+ ****************************************************************************/
596
+
597
+ #[ doc = "Indexes a task-local data slot. The function itself is used to
598
+ automatically finalise stored values; also, its code pointer is used for
599
+ comparison. Recommended use is to write an empty function for each desired
600
+ task-local data slot (and use class destructors, instead of code inside the
601
+ finaliser, if specific teardown is needed). DO NOT use multiple instantiations
602
+ of a single polymorphic function to index data of different types; arbitrary
603
+ type coercion is possible this way. The interface is safe as long as all key
604
+ functions are monomorphic." ]
605
+ type local_data_key < T > = fn @( +@T ) ;
606
+
607
+ // We use dvec because it's the best data structure in core. If TLS is used
608
+ // heavily in future, this could be made more efficient with a proper map.
609
+ type task_local_element = ( * libc:: c_void , * libc:: c_void , fn @( +* libc:: c_void ) ) ;
610
+ // Has to be a pointer at the outermost layer; the native call returns void *.
611
+ type task_local_map = @dvec:: dvec < option < task_local_element > > ;
612
+
613
+ crust fn cleanup_task_local_map ( map_ptr : * libc:: c_void ) unsafe {
614
+ assert !map_ptr. is_null ( ) ;
615
+ // Get and keep the single reference that was created at the beginning.
616
+ let map: task_local_map = unsafe :: reinterpret_cast ( map_ptr) ;
617
+ for ( * map) . each { |entry|
618
+ alt entry {
619
+ // Finaliser drops data. We drop the finaliser implicitly here.
620
+ some( ( _key, data, finalise_fn) ) { finalise_fn( data) ; }
621
+ none { }
622
+ }
623
+ }
624
+ }
625
+
626
+ // Gets the map from the runtime. Lazily initialises if not done so already.
627
+ unsafe fn get_task_local_map ( task : * rust_task ) -> task_local_map {
628
+ // Relies on the runtime initialising the pointer to null.
629
+ // NOTE: The map's box lives in TLS invisibly referenced once. Each time
630
+ // we retrieve it for get/set, we make another reference, which get/set
631
+ // drop when they finish. No "re-storing after modifying" is needed.
632
+ let map_ptr = rustrt:: rust_get_task_local_data ( task) ;
633
+ if map_ptr. is_null ( ) {
634
+ let map: task_local_map = @dvec:: dvec ( ) ;
635
+ // Use reinterpret_cast -- transmute would take map away from us also.
636
+ rustrt:: rust_set_task_local_data ( task, unsafe :: reinterpret_cast ( map) ) ;
637
+ rustrt:: rust_task_local_data_atexit ( task, cleanup_task_local_map) ;
638
+ // Also need to reference it an extra time to keep it for now.
639
+ unsafe :: bump_box_refcount ( map) ;
640
+ map
641
+ } else {
642
+ let map = unsafe :: transmute ( map_ptr) ;
643
+ unsafe :: bump_box_refcount ( map) ;
644
+ map
645
+ }
646
+ }
647
+
648
+ unsafe fn key_to_key_value < T > ( key : local_data_key < T > ) -> * libc:: c_void {
649
+ // Keys are closures, which are (fnptr,envptr) pairs. Use fnptr.
650
+ // Use reintepret_cast -- transmute would leak (forget) the closure.
651
+ let pair: ( * libc:: c_void , * libc:: c_void ) = unsafe :: reinterpret_cast ( key) ;
652
+ tuple:: first ( pair)
653
+ }
654
+
655
+ // If returning some(..), returns with @T with the map's reference. Careful!
656
+ unsafe fn local_data_lookup < T > ( map : task_local_map , key : local_data_key < T > )
657
+ -> option < ( uint , * libc:: c_void , fn @( +* libc:: c_void ) ) > {
658
+ let key_value = key_to_key_value ( key) ;
659
+ let map_pos = ( * map) . position { |entry|
660
+ alt entry { some( ( k, _, _) ) { k == key_value } none { false } }
661
+ } ;
662
+ map_pos. map { |index|
663
+ // .get() is guaranteed because of "none { false }" above.
664
+ let ( _, data_ptr, finaliser) = ( * map) [ index] . get ( ) ;
665
+ ( index, data_ptr, finaliser)
666
+ }
667
+ }
668
+
669
+ unsafe fn local_get_helper < T > ( task : * rust_task , key : local_data_key < T > ,
670
+ do_pop : bool ) -> option < @T > {
671
+ let map = get_task_local_map ( task) ;
672
+ // Interpret our findings from the map
673
+ local_data_lookup ( map, key) . map { |result|
674
+ // A reference count magically appears on 'data' out of thin air.
675
+ // 'data' has the reference we originally stored it with. We either
676
+ // need to erase it from the map or artificially bump the count.
677
+ let ( index, data_ptr, _) = result;
678
+ let data: @T = unsafe :: transmute ( data_ptr) ;
679
+ if do_pop {
680
+ ( * map) . set_elt ( index, none) ;
681
+ } else {
682
+ unsafe :: bump_box_refcount ( data) ;
683
+ }
684
+ data
685
+ }
686
+ }
687
+
688
+ unsafe fn local_pop < T > ( task : * rust_task ,
689
+ key : local_data_key < T > ) -> option < @T > {
690
+ local_get_helper ( task, key, true )
691
+ }
692
+
693
+ unsafe fn local_get < T > ( task : * rust_task ,
694
+ key : local_data_key < T > ) -> option < @T > {
695
+ local_get_helper ( task, key, false )
696
+ }
697
+
698
+ unsafe fn local_set < T > ( task : * rust_task , key : local_data_key < T > , -data : @T ) {
699
+ let map = get_task_local_map ( task) ;
700
+ // Store key+data as *voids. Data is invisibly referenced once; key isn't.
701
+ let keyval = key_to_key_value ( key) ;
702
+ let data_ptr = unsafe :: transmute ( data) ;
703
+ // Finaliser is called at task exit to de-reference up remaining entries.
704
+ let finaliser: fn @( +* libc:: c_void ) = unsafe :: reinterpret_cast ( key) ;
705
+ // Construct new entry to store in the map.
706
+ let new_entry = some ( ( keyval, data_ptr, finaliser) ) ;
707
+ // Find a place to put it.
708
+ alt local_data_lookup ( map, key) {
709
+ some ( ( index, old_data_ptr, old_finaliser) ) {
710
+ // Key already had a value set, old_data_ptr, whose reference we
711
+ // need to drop. After that, overwriting its slot will be safe.
712
+ // (The heap-allocated finaliser will be freed in the overwrite.)
713
+ // FIXME(2734): just transmuting old_data_ptr to @T doesn't work,
714
+ // similarly to the sample there (but more our/unsafety's fault?).
715
+ old_finaliser ( old_data_ptr) ;
716
+ ( * map) . set_elt ( index, new_entry) ;
717
+ }
718
+ none {
719
+ // Find an empty slot. If not, grow the vector.
720
+ alt ( * map) . position ( { |x| x == none} ) {
721
+ some ( empty_index) {
722
+ ( * map) . set_elt ( empty_index, new_entry) ;
723
+ }
724
+ none {
725
+ ( * map) . push ( new_entry) ;
726
+ }
727
+ }
728
+ }
729
+ }
730
+ }
731
+
732
+ unsafe fn local_modify < T > ( task : * rust_task , key : local_data_key < T > ,
733
+ modify_fn : fn ( option < @T > ) -> option < @T > ) {
734
+ // Could be more efficient by doing the lookup work, but this is easy.
735
+ let newdata = modify_fn ( local_pop ( task, key) ) ;
736
+ if newdata. is_some ( ) {
737
+ local_set ( task, key, option:: unwrap ( newdata) ) ;
738
+ }
739
+ }
740
+
741
+ /* Exported interface for task-local data (plus local_data_key above). */
742
+ #[ doc = "Remove a task-local data value from the table, returning the
743
+ reference that was originally created to insert it." ]
744
+ unsafe fn local_data_pop < T > ( key : local_data_key < T > ) -> option < @T > {
745
+ local_pop ( rustrt:: rust_get_task ( ) , key)
746
+ }
747
+ #[ doc = "Retrieve a task-local data value. It will also be kept alive in the
748
+ table until explicitly removed." ]
749
+ unsafe fn local_data_get < T > ( key : local_data_key < T > ) -> option < @T > {
750
+ local_get ( rustrt:: rust_get_task ( ) , key)
751
+ }
752
+ #[ doc = "Store a value in task-local data. If this key already has a value,
753
+ that value is overwritten (and its destructor is run)." ]
754
+ unsafe fn local_data_set < T > ( key : local_data_key < T > , -data : @T ) {
755
+ local_set ( rustrt:: rust_get_task ( ) , key, data)
756
+ }
757
+ #[ doc = "Modify a task-local data value. If the function returns 'none', the
758
+ data is removed (and its reference dropped)." ]
759
+ unsafe fn local_data_modify < T > ( key : local_data_key < T > ,
760
+ modify_fn : fn ( option < @T > ) -> option < @T > ) {
761
+ local_modify ( rustrt:: rust_get_task ( ) , key, modify_fn)
762
+ }
763
+
576
764
native mod rustrt {
577
765
#[ rust_stack]
578
766
fn rust_task_yield ( task : * rust_task , & killed: bool ) ;
@@ -596,6 +784,13 @@ native mod rustrt {
596
784
fn rust_osmain_sched_id ( ) -> sched_id ;
597
785
fn rust_task_inhibit_kill ( ) ;
598
786
fn rust_task_allow_kill ( ) ;
787
+
788
+ #[ rust_stack]
789
+ fn rust_get_task_local_data ( task : * rust_task ) -> * libc:: c_void ;
790
+ #[ rust_stack]
791
+ fn rust_set_task_local_data ( task : * rust_task , map : * libc:: c_void ) ;
792
+ #[ rust_stack]
793
+ fn rust_task_local_data_atexit ( task : * rust_task , cleanup_fn : * u8 ) ;
599
794
}
600
795
601
796
@@ -997,3 +1192,94 @@ fn test_unkillable() {
997
1192
// Now we can be killed
998
1193
po. recv ( ) ;
999
1194
}
1195
+
1196
+ #[ test]
1197
+ fn test_tls_multitask ( ) unsafe {
1198
+ fn my_key ( +_x : @str ) { }
1199
+ local_data_set ( my_key, @"parent data") ;
1200
+ task:: spawn { ||
1201
+ assert local_data_get( my_key) == none; // TLS shouldn't carry over.
1202
+ local_data_set( my_key, @"child data") ;
1203
+ assert * ( local_data_get ( my_key) . get ( ) ) == "child data" ;
1204
+ // should be cleaned up for us
1205
+ }
1206
+ // Must work multiple times
1207
+ assert * ( local_data_get ( my_key) . get ( ) ) == "parent data" ;
1208
+ assert * ( local_data_get ( my_key) . get ( ) ) == "parent data" ;
1209
+ assert * ( local_data_get ( my_key) . get ( ) ) == "parent data" ;
1210
+ }
1211
+
1212
+ #[ test]
1213
+ fn test_tls_overwrite ( ) unsafe {
1214
+ fn my_key ( +_x : @str ) { }
1215
+ local_data_set ( my_key, @"first data") ;
1216
+ local_data_set ( my_key, @"next data") ; // Shouldn't leak.
1217
+ assert * ( local_data_get ( my_key) . get ( ) ) == "next data" ;
1218
+ }
1219
+
1220
+ #[ test]
1221
+ fn test_tls_pop ( ) unsafe {
1222
+ fn my_key ( +_x : @str ) { }
1223
+ local_data_set ( my_key, @"weasel") ;
1224
+ assert * ( local_data_pop ( my_key) . get ( ) ) == "weasel" ;
1225
+ // Pop must remove the data from the map.
1226
+ assert local_data_pop ( my_key) == none;
1227
+ }
1228
+
1229
+ #[ test]
1230
+ fn test_tls_modify ( ) unsafe {
1231
+ fn my_key ( +_x : @str ) { }
1232
+ local_data_modify ( my_key) { |data|
1233
+ alt data {
1234
+ some( @val) { fail "unwelcome value: " + val }
1235
+ none { some( @"first data") }
1236
+ }
1237
+ }
1238
+ local_data_modify ( my_key) { |data|
1239
+ alt data {
1240
+ some( @"first data") { some( @"next data") }
1241
+ some( @val) { fail "wrong value: " + val }
1242
+ none { fail "missing value" }
1243
+ }
1244
+ }
1245
+ assert * ( local_data_pop( my_key) . get( ) ) == "next data";
1246
+ }
1247
+
1248
+ #[ test]
1249
+ fn test_tls_crust_automorestack_memorial_bug( ) unsafe {
1250
+ // This might result in a stack-canary clobber if the runtime fails to set
1251
+ // sp_limit to 0 when calling the cleanup crust - it might automatically
1252
+ // jump over to the rust stack, which causes next_c_sp to get recorded as
1253
+ // something within a rust stack segment. Then a subsequent upcall (esp.
1254
+ // for logging, think vsnprintf) would run on a stack smaller than 1 MB.
1255
+ fn my_key( +_x: @str ) { }
1256
+ task:: spawn { ||
1257
+ unsafe { local_data_set( my_key, @"hax") ; }
1258
+ }
1259
+ }
1260
+
1261
+ #[ test]
1262
+ fn test_tls_multiple_types( ) unsafe {
1263
+ fn str_key( +_x: @str ) { }
1264
+ fn box_key( +_x: @@( ) ) { }
1265
+ fn int_key( +_x: @int) { }
1266
+ task:: spawn{ ||
1267
+ local_data_set( str_key, @"string data") ;
1268
+ local_data_set( box_key, @@( ) ) ;
1269
+ local_data_set( int_key, @42 ) ;
1270
+ }
1271
+ }
1272
+
1273
+ #[ test]
1274
+ fn test_tls_overwrite_multiple_types( ) unsafe {
1275
+ fn str_key( +_x: @str ) { }
1276
+ fn box_key( +_x: @@( ) ) { }
1277
+ fn int_key( +_x: @int) { }
1278
+ task:: spawn{ ||
1279
+ local_data_set( str_key, @"string data") ;
1280
+ local_data_set( int_key, @42 ) ;
1281
+ // This could cause a segfault if overwriting-destruction is done with
1282
+ // the crazy polymorphic transmute rather than the provided finaliser.
1283
+ local_data_set( int_key, @31337 ) ;
1284
+ }
1285
+ }
0 commit comments