diff options
| author | Brian Anderson <banderson@mozilla.com> | 2012-09-19 17:29:54 -0700 |
|---|---|---|
| committer | Brian Anderson <banderson@mozilla.com> | 2012-09-19 19:08:32 -0700 |
| commit | 1397dca2dd07b93789226a22b2ccc456d0a5317a (patch) | |
| tree | f12c69d4b48ee5a1630feedc9c3ad48538938255 /src/libcore/task | |
| parent | 1643794e0190ed2284bedcdd3fcbc44187a4a98f (diff) | |
| download | rust-1397dca2dd07b93789226a22b2ccc456d0a5317a.tar.gz rust-1397dca2dd07b93789226a22b2ccc456d0a5317a.zip | |
core: Move TLS to task::local_data
Diffstat (limited to 'src/libcore/task')
| -rw-r--r-- | src/libcore/task/local_data.rs | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/src/libcore/task/local_data.rs b/src/libcore/task/local_data.rs new file mode 100644 index 00000000000..4fbf0475c0a --- /dev/null +++ b/src/libcore/task/local_data.rs @@ -0,0 +1,346 @@ +/*! + +Task local data management + +Allows storing boxes with arbitrary types inside, to be accessed anywhere +within a task, keyed by a pointer to a global finaliser function. Useful +for task-spawning metadata (tracking linked failure state), dynamic +variables, and interfacing with foreign code with bad callback interfaces. + +To use, declare a monomorphic global function at the type to store, and use +it as the 'key' when accessing. See the 'tls' tests below for examples. + +Casting 'Arcane Sight' reveals an overwhelming aura of Transmutation magic. + +*/ + +export local_data_key; +export local_data_pop; +export local_data_get; +export local_data_set; +export local_data_modify; + +// XXX: These shouldn't be exported but they are used by task.rs +export local_get; +export local_set; + +/** + * Indexes a task-local data slot. The function's code pointer is used for + * comparison. Recommended use is to write an empty function for each desired + * task-local data slot (and use class destructors, not code inside the + * function, if specific teardown is needed). DO NOT use multiple + * instantiations of a single polymorphic function to index data of different + * types; arbitrary type coercion is possible this way. + * + * One other exception is that this global state can be used in a destructor + * context to create a circular @-box reference, which will crash during task + * failure (see issue #3039). + * + * These two cases aside, the interface is safe. + */ +type LocalDataKey<T: Owned> = &fn(+@T); + +trait LocalData { } +impl<T: Owned> @T: LocalData { } + +impl LocalData: Eq { + pure fn eq(&&other: LocalData) -> bool unsafe { + let ptr_a: (uint, uint) = cast::reinterpret_cast(&self); + let ptr_b: (uint, uint) = cast::reinterpret_cast(&other); + return ptr_a == ptr_b; + } + pure fn ne(&&other: LocalData) -> bool { !self.eq(other) } +} + +// We use dvec because it's the best data structure in core. If TLS is used +// heavily in future, this could be made more efficient with a proper map. +type TaskLocalElement = (*libc::c_void, *libc::c_void, LocalData); +// Has to be a pointer at outermost layer; the foreign call returns void *. +type TaskLocalMap = @dvec::DVec<Option<TaskLocalElement>>; + +extern fn cleanup_task_local_map(map_ptr: *libc::c_void) unsafe { + assert !map_ptr.is_null(); + // Get and keep the single reference that was created at the beginning. + let _map: TaskLocalMap = cast::reinterpret_cast(&map_ptr); + // All local_data will be destroyed along with the map. +} + +// Gets the map from the runtime. Lazily initialises if not done so already. +unsafe fn get_task_local_map(task: *rust_task) -> TaskLocalMap { + + // Relies on the runtime initialising the pointer to null. + // NOTE: The map's box lives in TLS invisibly referenced once. Each time + // we retrieve it for get/set, we make another reference, which get/set + // drop when they finish. No "re-storing after modifying" is needed. + let map_ptr = rustrt::rust_get_task_local_data(task); + if map_ptr.is_null() { + let map: TaskLocalMap = @dvec::DVec(); + // Use reinterpret_cast -- transmute would take map away from us also. + rustrt::rust_set_task_local_data( + task, cast::reinterpret_cast(&map)); + rustrt::rust_task_local_data_atexit(task, cleanup_task_local_map); + // Also need to reference it an extra time to keep it for now. + cast::bump_box_refcount(map); + map + } else { + let map = cast::transmute(move map_ptr); + cast::bump_box_refcount(map); + map + } +} + +unsafe fn key_to_key_value<T: Owned>( + key: LocalDataKey<T>) -> *libc::c_void { + + // Keys are closures, which are (fnptr,envptr) pairs. Use fnptr. + // Use reintepret_cast -- transmute would leak (forget) the closure. + let pair: (*libc::c_void, *libc::c_void) = cast::reinterpret_cast(&key); + pair.first() +} + +// If returning Some(..), returns with @T with the map's reference. Careful! +unsafe fn local_data_lookup<T: Owned>( + map: TaskLocalMap, key: LocalDataKey<T>) + -> Option<(uint, *libc::c_void)> { + + let key_value = key_to_key_value(key); + let map_pos = (*map).position(|entry| + match entry { + Some((k,_,_)) => k == key_value, + None => false + } + ); + do map_pos.map |index| { + // .get() is guaranteed because of "None { false }" above. + let (_, data_ptr, _) = (*map)[index].get(); + (index, data_ptr) + } +} + +unsafe fn local_get_helper<T: Owned>( + task: *rust_task, key: LocalDataKey<T>, + do_pop: bool) -> Option<@T> { + + let map = get_task_local_map(task); + // Interpreturn our findings from the map + do local_data_lookup(map, key).map |result| { + // A reference count magically appears on 'data' out of thin air. It + // was referenced in the local_data box, though, not here, so before + // overwriting the local_data_box we need to give an extra reference. + // We must also give an extra reference when not removing. + let (index, data_ptr) = result; + let data: @T = cast::transmute(move data_ptr); + cast::bump_box_refcount(data); + if do_pop { + (*map).set_elt(index, None); + } + data + } +} + +unsafe fn local_pop<T: Owned>( + task: *rust_task, + key: LocalDataKey<T>) -> Option<@T> { + + local_get_helper(task, key, true) +} + +unsafe fn local_get<T: Owned>( + task: *rust_task, + key: LocalDataKey<T>) -> Option<@T> { + + local_get_helper(task, key, false) +} + +unsafe fn local_set<T: Owned>( + task: *rust_task, key: LocalDataKey<T>, +data: @T) { + + let map = get_task_local_map(task); + // Store key+data as *voids. Data is invisibly referenced once; key isn't. + let keyval = key_to_key_value(key); + // We keep the data in two forms: one as an unsafe pointer, so we can get + // it back by casting; another in an existential box, so the reference we + // own on it can be dropped when the box is destroyed. The unsafe pointer + // does not have a reference associated with it, so it may become invalid + // when the box is destroyed. + let data_ptr = cast::reinterpret_cast(&data); + let data_box = data as LocalData; + // Construct new entry to store in the map. + let new_entry = Some((keyval, data_ptr, data_box)); + // Find a place to put it. + match local_data_lookup(map, key) { + Some((index, _old_data_ptr)) => { + // Key already had a value set, _old_data_ptr, whose reference + // will get dropped when the local_data box is overwritten. + (*map).set_elt(index, new_entry); + } + None => { + // Find an empty slot. If not, grow the vector. + match (*map).position(|x| x.is_none()) { + Some(empty_index) => (*map).set_elt(empty_index, new_entry), + None => (*map).push(new_entry) + } + } + } +} + +unsafe fn local_modify<T: Owned>( + task: *rust_task, key: LocalDataKey<T>, + modify_fn: fn(Option<@T>) -> Option<@T>) { + + // Could be more efficient by doing the lookup work, but this is easy. + let newdata = modify_fn(local_pop(task, key)); + if newdata.is_some() { + local_set(task, key, option::unwrap(move newdata)); + } +} + +/* Exported interface for task-local data (plus local_data_key above). */ +/** + * Remove a task-local data value from the table, returning the + * reference that was originally created to insert it. + */ +unsafe fn local_data_pop<T: Owned>( + key: LocalDataKey<T>) -> Option<@T> { + + local_pop(rustrt::rust_get_task(), key) +} +/** + * Retrieve a task-local data value. It will also be kept alive in the + * table until explicitly removed. + */ +unsafe fn local_data_get<T: Owned>( + key: LocalDataKey<T>) -> Option<@T> { + + local_get(rustrt::rust_get_task(), key) +} +/** + * Store a value in task-local data. If this key already has a value, + * that value is overwritten (and its destructor is run). + */ +unsafe fn local_data_set<T: Owned>( + key: LocalDataKey<T>, +data: @T) { + + local_set(rustrt::rust_get_task(), key, data) +} +/** + * Modify a task-local data value. If the function returns 'None', the + * data is removed (and its reference dropped). + */ +unsafe fn local_data_modify<T: Owned>( + key: LocalDataKey<T>, + modify_fn: fn(Option<@T>) -> Option<@T>) { + + local_modify(rustrt::rust_get_task(), key, modify_fn) +} + +#[test] +fn test_tls_multitask() unsafe { + fn my_key(+_x: @~str) { } + local_data_set(my_key, @~"parent data"); + do task::spawn unsafe { + assert local_data_get(my_key).is_none(); // TLS shouldn't carry over. + local_data_set(my_key, @~"child data"); + assert *(local_data_get(my_key).get()) == ~"child data"; + // should be cleaned up for us + } + // Must work multiple times + assert *(local_data_get(my_key).get()) == ~"parent data"; + assert *(local_data_get(my_key).get()) == ~"parent data"; + assert *(local_data_get(my_key).get()) == ~"parent data"; +} + +#[test] +fn test_tls_overwrite() unsafe { + fn my_key(+_x: @~str) { } + local_data_set(my_key, @~"first data"); + local_data_set(my_key, @~"next data"); // Shouldn't leak. + assert *(local_data_get(my_key).get()) == ~"next data"; +} + +#[test] +fn test_tls_pop() unsafe { + fn my_key(+_x: @~str) { } + local_data_set(my_key, @~"weasel"); + assert *(local_data_pop(my_key).get()) == ~"weasel"; + // Pop must remove the data from the map. + assert local_data_pop(my_key).is_none(); +} + +#[test] +fn test_tls_modify() unsafe { + fn my_key(+_x: @~str) { } + local_data_modify(my_key, |data| { + match data { + Some(@val) => fail ~"unwelcome value: " + val, + None => Some(@~"first data") + } + }); + local_data_modify(my_key, |data| { + match data { + Some(@~"first data") => Some(@~"next data"), + Some(@val) => fail ~"wrong value: " + val, + None => fail ~"missing value" + } + }); + assert *(local_data_pop(my_key).get()) == ~"next data"; +} + +#[test] +fn test_tls_crust_automorestack_memorial_bug() unsafe { + // This might result in a stack-canary clobber if the runtime fails to set + // sp_limit to 0 when calling the cleanup extern - it might automatically + // jump over to the rust stack, which causes next_c_sp to get recorded as + // Something within a rust stack segment. Then a subsequent upcall (esp. + // for logging, think vsnprintf) would run on a stack smaller than 1 MB. + fn my_key(+_x: @~str) { } + do task::spawn { + unsafe { local_data_set(my_key, @~"hax"); } + } +} + +#[test] +fn test_tls_multiple_types() unsafe { + fn str_key(+_x: @~str) { } + fn box_key(+_x: @@()) { } + fn int_key(+_x: @int) { } + do task::spawn unsafe { + local_data_set(str_key, @~"string data"); + local_data_set(box_key, @@()); + local_data_set(int_key, @42); + } +} + +#[test] +fn test_tls_overwrite_multiple_types() { + fn str_key(+_x: @~str) { } + fn box_key(+_x: @@()) { } + fn int_key(+_x: @int) { } + do task::spawn unsafe { + local_data_set(str_key, @~"string data"); + local_data_set(int_key, @42); + // This could cause a segfault if overwriting-destruction is done with + // the crazy polymorphic transmute rather than the provided finaliser. + local_data_set(int_key, @31337); + } +} + +#[test] +#[should_fail] +#[ignore(cfg(windows))] +fn test_tls_cleanup_on_failure() unsafe { + fn str_key(+_x: @~str) { } + fn box_key(+_x: @@()) { } + fn int_key(+_x: @int) { } + local_data_set(str_key, @~"parent data"); + local_data_set(box_key, @@()); + do task::spawn unsafe { // spawn_linked + local_data_set(str_key, @~"string data"); + local_data_set(box_key, @@()); + local_data_set(int_key, @42); + fail; + } + // Not quite nondeterministic. + local_data_set(int_key, @31337); + fail; +} |
