core: Move TLS to task::local_data
This commit is contained in:
parent
1643794e01
commit
1397dca2dd
6 changed files with 372 additions and 351 deletions
|
|
@ -219,7 +219,9 @@ mod send_map;
|
|||
|
||||
// Concurrency
|
||||
mod comm;
|
||||
mod task;
|
||||
mod task {
|
||||
mod local_data;
|
||||
}
|
||||
mod future;
|
||||
mod pipes;
|
||||
|
||||
|
|
|
|||
|
|
@ -66,11 +66,7 @@ export get_task;
|
|||
export unkillable, rekillable;
|
||||
export atomically;
|
||||
|
||||
export local_data_key;
|
||||
export local_data_pop;
|
||||
export local_data_get;
|
||||
export local_data_set;
|
||||
export local_data_modify;
|
||||
export local_data;
|
||||
|
||||
export SingleThreaded;
|
||||
export ThreadPerCore;
|
||||
|
|
@ -1213,7 +1209,7 @@ fn gen_child_taskgroup(linked: bool, supervised: bool)
|
|||
/*######################################################################*
|
||||
* Step 1. Get spawner's taskgroup info.
|
||||
*######################################################################*/
|
||||
let spawner_group = match unsafe { local_get(spawner,
|
||||
let spawner_group = match unsafe { local_data::local_get(spawner,
|
||||
taskgroup_key!()) } {
|
||||
None => {
|
||||
// Main task, doing first spawn ever. Lazily initialise here.
|
||||
|
|
@ -1225,7 +1221,9 @@ fn gen_child_taskgroup(linked: bool, supervised: bool)
|
|||
// Main task/group has no ancestors, no notifier, etc.
|
||||
let group =
|
||||
@TCB(spawner, move tasks, AncestorList(None), true, None);
|
||||
unsafe { local_set(spawner, taskgroup_key!(), group); }
|
||||
unsafe {
|
||||
local_data::local_set(spawner, taskgroup_key!(), group);
|
||||
}
|
||||
group
|
||||
}
|
||||
Some(group) => group
|
||||
|
|
@ -1352,7 +1350,9 @@ fn spawn_raw(+opts: TaskOpts, +f: fn~()) {
|
|||
if enlist_many(child, &child_arc, &mut ancestors) {
|
||||
let group = @TCB(child, move child_arc, move ancestors,
|
||||
is_main, move notifier);
|
||||
unsafe { local_set(child, taskgroup_key!(), group); }
|
||||
unsafe {
|
||||
local_data::local_set(child, taskgroup_key!(), group);
|
||||
}
|
||||
|
||||
// Run the child's body.
|
||||
f();
|
||||
|
|
@ -1429,230 +1429,6 @@ fn spawn_raw(+opts: TaskOpts, +f: fn~()) {
|
|||
}
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* 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.
|
||||
****************************************************************************/
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
|
||||
extern mod rustrt {
|
||||
#[rust_stack]
|
||||
fn rust_task_yield(task: *rust_task) -> bool;
|
||||
|
|
@ -2317,117 +2093,6 @@ fn test_child_doesnt_ref_parent() {
|
|||
task::spawn(child_no(0));
|
||||
}
|
||||
|
||||
#[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;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sched_thread_per_core() {
|
||||
let (chan, port) = pipes::stream();
|
||||
|
|
|
|||
346
src/libcore/task/local_data.rs
Normal file
346
src/libcore/task/local_data.rs
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -39,7 +39,9 @@ macro_rules! interner_key (
|
|||
)
|
||||
|
||||
fn serialize_ident<S: Serializer>(s: S, i: ident) {
|
||||
let intr = match unsafe{task::local_data_get(interner_key!())}{
|
||||
let intr = match unsafe{
|
||||
task::local_data::local_data_get(interner_key!())
|
||||
} {
|
||||
None => fail ~"serialization: TLS interner not set up",
|
||||
Some(intr) => intr
|
||||
};
|
||||
|
|
@ -47,7 +49,9 @@ fn serialize_ident<S: Serializer>(s: S, i: ident) {
|
|||
s.emit_str(*(*intr).get(i));
|
||||
}
|
||||
fn deserialize_ident<D: Deserializer>(d: D) -> ident {
|
||||
let intr = match unsafe{task::local_data_get(interner_key!())}{
|
||||
let intr = match unsafe{
|
||||
task::local_data::local_data_get(interner_key!())
|
||||
} {
|
||||
None => fail ~"deserialization: TLS interner not set up",
|
||||
Some(intr) => intr
|
||||
};
|
||||
|
|
|
|||
|
|
@ -348,8 +348,12 @@ fn mk_ident_interner() -> ident_interner {
|
|||
let rv = interner::mk_prefill::<@~str>(init_vec);
|
||||
|
||||
/* having multiple interners will just confuse the serializer */
|
||||
unsafe{ assert task::local_data_get(interner_key!()).is_none() };
|
||||
unsafe{ task::local_data_set(interner_key!(), @rv) };
|
||||
unsafe {
|
||||
assert task::local_data::local_data_get(interner_key!()).is_none()
|
||||
};
|
||||
unsafe {
|
||||
task::local_data::local_data_set(interner_key!(), @rv)
|
||||
};
|
||||
rv
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
use syntax::ast;
|
||||
use doc::ItemUtils;
|
||||
use task::local_data::local_data_get;
|
||||
|
||||
export from_srv, extract, to_str, interner;
|
||||
|
||||
|
||||
/* can't import macros yet, so this is copied from token.rs. See its comment
|
||||
* there. */
|
||||
macro_rules! interner_key (
|
||||
|
|
@ -16,13 +16,13 @@ macro_rules! interner_key (
|
|||
// Hack; rather than thread an interner through everywhere, rely on
|
||||
// thread-local data
|
||||
fn to_str(id: ast::ident) -> ~str {
|
||||
let intr = unsafe{ task::local_data_get(interner_key!()) };
|
||||
let intr = unsafe{ local_data_get(interner_key!()) };
|
||||
|
||||
return *(*intr.get()).get(id);
|
||||
}
|
||||
|
||||
fn interner() -> syntax::parse::token::ident_interner {
|
||||
return *(unsafe{ task::local_data_get(interner_key!()) }).get();
|
||||
return *(unsafe{ local_data_get(interner_key!()) }).get();
|
||||
}
|
||||
|
||||
fn from_srv(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue