rust/src/libstd/local_data.rs

231 lines
6.9 KiB
Rust
Raw Normal View History

// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
2012-09-19 19:29:54 -05:00
/*!
Task local data management
2012-09-19 19:43:41 -05:00
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 dynamic variables, singletons, and interfacing
with foreign code with bad callback interfaces.
2012-09-19 19:29:54 -05:00
2012-09-19 19:43:41 -05:00
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.
2012-09-19 19:29:54 -05:00
2012-09-19 19:43:41 -05:00
Casting 'Arcane Sight' reveals an overwhelming aura of Transmutation
magic.
2012-09-19 19:29:54 -05:00
*/
use prelude::*;
use task::local_data_priv::{local_get, local_pop, local_set, Handle};
#[cfg(test)] use task;
2012-09-19 19:29:54 -05:00
/**
* 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.
*/
pub type LocalDataKey<'self,T> = &'self fn:Copy(v: @T);
2012-09-19 19:29:54 -05:00
/**
* Remove a task-local data value from the table, returning the
* reference that was originally created to insert it.
*/
2013-05-07 16:43:48 -05:00
pub unsafe fn local_data_pop<T: 'static>(
2012-09-19 19:29:54 -05:00
key: LocalDataKey<T>) -> Option<@T> {
local_pop(Handle::new(), key)
2012-09-19 19:29:54 -05:00
}
/**
* Retrieve a task-local data value. It will also be kept alive in the
* table until explicitly removed.
*/
2013-05-07 16:43:48 -05:00
pub unsafe fn local_data_get<T: 'static>(
2012-09-19 19:29:54 -05:00
key: LocalDataKey<T>) -> Option<@T> {
local_get(Handle::new(), key)
2012-09-19 19:29:54 -05:00
}
/**
* Store a value in task-local data. If this key already has a value,
* that value is overwritten (and its destructor is run).
*/
2013-05-07 16:43:48 -05:00
pub unsafe fn local_data_set<T: 'static>(
key: LocalDataKey<T>, data: @T) {
2012-09-19 19:29:54 -05:00
local_set(Handle::new(), key, data)
2012-09-19 19:29:54 -05:00
}
/**
* Modify a task-local data value. If the function returns 'None', the
* data is removed (and its reference dropped).
*/
2013-05-07 16:43:48 -05:00
pub unsafe fn local_data_modify<T: 'static>(
2012-09-19 19:29:54 -05:00
key: LocalDataKey<T>,
modify_fn: &fn(Option<@T>) -> Option<@T>) {
2012-09-19 19:29:54 -05:00
let cur = local_data_pop(key);
match modify_fn(cur) {
Some(next) => { local_data_set(key, next); }
None => {}
}
2012-09-19 19:29:54 -05:00
}
#[test]
fn test_tls_multitask() {
unsafe {
fn my_key(_x: @~str) { }
local_data_set(my_key, @~"parent data");
do task::spawn {
2013-06-27 10:45:24 -05:00
// TLS shouldn't carry over.
assert!(local_data_get(my_key).is_none());
local_data_set(my_key, @~"child data");
assert!(*(local_data_get(my_key).get()) ==
2013-03-06 21:09:17 -06:00
~"child data");
2013-06-27 10:45:24 -05:00
// should be cleaned up for us
}
// Must work multiple times
2013-03-28 20:39:09 -05:00
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");
2012-09-19 19:29:54 -05:00
}
}
#[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.
2013-03-28 20:39:09 -05:00
assert!(*(local_data_get(my_key).get()) == ~"next data");
}
2012-09-19 19:29:54 -05:00
}
#[test]
fn test_tls_pop() {
unsafe {
fn my_key(_x: @~str) { }
local_data_set(my_key, @~"weasel");
2013-03-28 20:39:09 -05:00
assert!(*(local_data_pop(my_key).get()) == ~"weasel");
// Pop must remove the data from the map.
2013-03-28 20:39:09 -05:00
assert!(local_data_pop(my_key).is_none());
}
2012-09-19 19:29:54 -05:00
}
#[test]
fn test_tls_modify() {
unsafe {
fn my_key(_x: @~str) { }
local_data_modify(my_key, |data| {
match data {
Some(@ref val) => fail!("unwelcome value: %s", *val),
2013-05-09 06:52:07 -05:00
None => Some(@~"first data")
}
});
local_data_modify(my_key, |data| {
match data {
Some(@~"first data") => Some(@~"next data"),
2013-05-09 06:52:07 -05:00
Some(@ref val) => fail!("wrong value: %s", *val),
None => fail!("missing value")
}
});
2013-03-28 20:39:09 -05:00
assert!(*(local_data_pop(my_key).get()) == ~"next data");
}
2012-09-19 19:29:54 -05:00
}
#[test]
fn test_tls_crust_automorestack_memorial_bug() {
// 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"); }
2012-09-19 19:29:54 -05:00
}
}
#[test]
fn test_tls_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(box_key, @@());
local_data_set(int_key, @42);
}
2012-09-19 19:29:54 -05:00
}
}
#[test]
2012-10-04 00:06:51 -05:00
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);
}
2012-09-19 19:29:54 -05:00
}
}
#[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");
2012-09-19 19:29:54 -05:00
local_data_set(box_key, @@());
do task::spawn {
2013-06-27 10:45:24 -05:00
// 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!();
2012-09-19 19:29:54 -05:00
}
}
#[test]
fn test_static_pointer() {
unsafe {
fn key(_x: @&'static int) { }
static VALUE: int = 0;
local_data_set(key, @&VALUE);
}
}