auto merge of #7841 : alexcrichton/rust/tls++, r=huonw
Simulates borrow checks for '@mut' boxes, or at least it's the same idea. This allows you to store owned values, but mutate them while they're owned by TLS. This should remove the necessity for a `pop`/`set` pattern to mutate data structures in TLS.
This commit is contained in:
commit
c032dddf6f
|
@ -41,7 +41,7 @@ magic.
|
||||||
|
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
|
|
||||||
use task::local_data_priv::{local_get, local_pop, local_set, Handle};
|
use task::local_data_priv::*;
|
||||||
|
|
||||||
#[cfg(test)] use task;
|
#[cfg(test)] use task;
|
||||||
|
|
||||||
|
@ -95,6 +95,13 @@ pub fn get<T: 'static, U>(key: Key<@T>, f: &fn(Option<&@T>) -> U) -> U {
|
||||||
pub fn get<T: 'static, U>(key: Key<T>, f: &fn(Option<&T>) -> U) -> U {
|
pub fn get<T: 'static, U>(key: Key<T>, f: &fn(Option<&T>) -> U) -> U {
|
||||||
unsafe { local_get(Handle::new(), key, f) }
|
unsafe { local_get(Handle::new(), key, f) }
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Retrieve a mutable borrowed pointer to a task-local data value.
|
||||||
|
*/
|
||||||
|
#[cfg(not(stage0))]
|
||||||
|
pub fn get_mut<T: 'static, U>(key: Key<T>, f: &fn(Option<&mut T>) -> U) -> U {
|
||||||
|
unsafe { local_get_mut(Handle::new(), key, f) }
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Store a value in task-local data. If this key already has a value,
|
* Store a value in task-local data. If this key already has a value,
|
||||||
* that value is overwritten (and its destructor is run).
|
* that value is overwritten (and its destructor is run).
|
||||||
|
@ -262,6 +269,34 @@ fn test_static_pointer() {
|
||||||
fn test_owned() {
|
fn test_owned() {
|
||||||
static key: Key<~int> = &Key;
|
static key: Key<~int> = &Key;
|
||||||
set(key, ~1);
|
set(key, ~1);
|
||||||
|
|
||||||
|
do get(key) |v| {
|
||||||
|
do get(key) |v| {
|
||||||
|
do get(key) |v| {
|
||||||
|
assert_eq!(**v.unwrap(), 1);
|
||||||
|
}
|
||||||
|
assert_eq!(**v.unwrap(), 1);
|
||||||
|
}
|
||||||
|
assert_eq!(**v.unwrap(), 1);
|
||||||
|
}
|
||||||
|
set(key, ~2);
|
||||||
|
do get(key) |v| {
|
||||||
|
assert_eq!(**v.unwrap(), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_mut() {
|
||||||
|
static key: Key<int> = &Key;
|
||||||
|
set(key, 1);
|
||||||
|
|
||||||
|
do get_mut(key) |v| {
|
||||||
|
*v.unwrap() = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
do get(key) |v| {
|
||||||
|
assert_eq!(*v.unwrap(), 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -283,3 +318,43 @@ fn test_same_key_type() {
|
||||||
get(key4, |x| assert_eq!(*x.unwrap(), 4));
|
get(key4, |x| assert_eq!(*x.unwrap(), 4));
|
||||||
get(key5, |x| assert_eq!(*x.unwrap(), 5));
|
get(key5, |x| assert_eq!(*x.unwrap(), 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_fail]
|
||||||
|
fn test_nested_get_set1() {
|
||||||
|
static key: Key<int> = &Key;
|
||||||
|
set(key, 4);
|
||||||
|
do get(key) |_| {
|
||||||
|
set(key, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_fail]
|
||||||
|
fn test_nested_get_mut2() {
|
||||||
|
static key: Key<int> = &Key;
|
||||||
|
set(key, 4);
|
||||||
|
do get(key) |_| {
|
||||||
|
get_mut(key, |_| {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_fail]
|
||||||
|
fn test_nested_get_mut3() {
|
||||||
|
static key: Key<int> = &Key;
|
||||||
|
set(key, 4);
|
||||||
|
do get_mut(key) |_| {
|
||||||
|
get(key, |_| {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_fail]
|
||||||
|
fn test_nested_get_mut4() {
|
||||||
|
static key: Key<int> = &Key;
|
||||||
|
set(key, 4);
|
||||||
|
do get_mut(key) |_| {
|
||||||
|
get_mut(key, |_| {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -44,6 +44,21 @@ impl Handle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deriving(Eq)]
|
||||||
|
enum LoanState {
|
||||||
|
NoLoan, ImmLoan, MutLoan
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoanState {
|
||||||
|
fn describe(&self) -> &'static str {
|
||||||
|
match *self {
|
||||||
|
NoLoan => "no loan",
|
||||||
|
ImmLoan => "immutable",
|
||||||
|
MutLoan => "mutable"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trait LocalData {}
|
trait LocalData {}
|
||||||
impl<T: 'static> LocalData for T {}
|
impl<T: 'static> LocalData for T {}
|
||||||
|
|
||||||
|
@ -77,7 +92,7 @@ impl<T: 'static> LocalData for T {}
|
||||||
//
|
//
|
||||||
// n.b. If TLS is used heavily in future, this could be made more efficient with
|
// n.b. If TLS is used heavily in future, this could be made more efficient with
|
||||||
// a proper map.
|
// a proper map.
|
||||||
type TaskLocalMap = ~[Option<(*libc::c_void, TLSValue, uint)>];
|
type TaskLocalMap = ~[Option<(*libc::c_void, TLSValue, LoanState)>];
|
||||||
type TLSValue = ~LocalData:;
|
type TLSValue = ~LocalData:;
|
||||||
|
|
||||||
fn cleanup_task_local_map(map_ptr: *libc::c_void) {
|
fn cleanup_task_local_map(map_ptr: *libc::c_void) {
|
||||||
|
@ -152,9 +167,10 @@ pub unsafe fn local_pop<T: 'static>(handle: Handle,
|
||||||
|
|
||||||
for map.mut_iter().advance |entry| {
|
for map.mut_iter().advance |entry| {
|
||||||
match *entry {
|
match *entry {
|
||||||
Some((k, _, loans)) if k == key_value => {
|
Some((k, _, loan)) if k == key_value => {
|
||||||
if loans != 0 {
|
if loan != NoLoan {
|
||||||
fail!("TLS value has been loaned via get already");
|
fail!("TLS value cannot be removed because it is already \
|
||||||
|
borrowed as %s", loan.describe());
|
||||||
}
|
}
|
||||||
// Move the data out of the `entry` slot via util::replace. This
|
// Move the data out of the `entry` slot via util::replace. This
|
||||||
// is guaranteed to succeed because we already matched on `Some`
|
// is guaranteed to succeed because we already matched on `Some`
|
||||||
|
@ -192,6 +208,29 @@ pub unsafe fn local_pop<T: 'static>(handle: Handle,
|
||||||
pub unsafe fn local_get<T: 'static, U>(handle: Handle,
|
pub unsafe fn local_get<T: 'static, U>(handle: Handle,
|
||||||
key: local_data::Key<T>,
|
key: local_data::Key<T>,
|
||||||
f: &fn(Option<&T>) -> U) -> U {
|
f: &fn(Option<&T>) -> U) -> U {
|
||||||
|
local_get_with(handle, key, ImmLoan, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn local_get_mut<T: 'static, U>(handle: Handle,
|
||||||
|
key: local_data::Key<T>,
|
||||||
|
f: &fn(Option<&mut T>) -> U) -> U {
|
||||||
|
do local_get_with(handle, key, MutLoan) |x| {
|
||||||
|
match x {
|
||||||
|
None => f(None),
|
||||||
|
// We're violating a lot of compiler guarantees with this
|
||||||
|
// invocation of `transmute_mut`, but we're doing runtime checks to
|
||||||
|
// ensure that it's always valid (only one at a time).
|
||||||
|
//
|
||||||
|
// there is no need to be upset!
|
||||||
|
Some(x) => { f(Some(cast::transmute_mut(x))) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn local_get_with<T: 'static, U>(handle: Handle,
|
||||||
|
key: local_data::Key<T>,
|
||||||
|
state: LoanState,
|
||||||
|
f: &fn(Option<&T>) -> U) -> U {
|
||||||
// This function must be extremely careful. Because TLS can store owned
|
// This function must be extremely careful. Because TLS can store owned
|
||||||
// values, and we must have some form of `get` function other than `pop`,
|
// values, and we must have some form of `get` function other than `pop`,
|
||||||
// this function has to give a `&` reference back to the caller.
|
// this function has to give a `&` reference back to the caller.
|
||||||
|
@ -218,12 +257,24 @@ pub unsafe fn local_get<T: 'static, U>(handle: Handle,
|
||||||
None => { return f(None); }
|
None => { return f(None); }
|
||||||
Some(i) => {
|
Some(i) => {
|
||||||
let ret;
|
let ret;
|
||||||
|
let mut return_loan = false;
|
||||||
match map[i] {
|
match map[i] {
|
||||||
Some((_, ref data, ref mut loans)) => {
|
Some((_, ref data, ref mut loan)) => {
|
||||||
*loans = *loans + 1;
|
match (state, *loan) {
|
||||||
|
(_, NoLoan) => {
|
||||||
|
*loan = state;
|
||||||
|
return_loan = true;
|
||||||
|
}
|
||||||
|
(ImmLoan, ImmLoan) => {}
|
||||||
|
(want, cur) => {
|
||||||
|
fail!("TLS slot cannot be borrowed as %s because \
|
||||||
|
it is already borrowed as %s",
|
||||||
|
want.describe(), cur.describe());
|
||||||
|
}
|
||||||
|
}
|
||||||
// data was created with `~~T as ~LocalData`, so we extract
|
// data was created with `~~T as ~LocalData`, so we extract
|
||||||
// pointer part of the trait, (as ~~T), and then use
|
// pointer part of the trait, (as ~~T), and then use
|
||||||
// compiler coercions to achieve a '&' pointer
|
// compiler coercions to achieve a '&' pointer.
|
||||||
match *cast::transmute::<&TLSValue, &(uint, ~~T)>(data) {
|
match *cast::transmute::<&TLSValue, &(uint, ~~T)>(data) {
|
||||||
(_vtable, ref box) => {
|
(_vtable, ref box) => {
|
||||||
let value: &T = **box;
|
let value: &T = **box;
|
||||||
|
@ -238,9 +289,11 @@ pub unsafe fn local_get<T: 'static, U>(handle: Handle,
|
||||||
// 'f' returned because `f` could have appended more TLS items which
|
// 'f' returned because `f` could have appended more TLS items which
|
||||||
// in turn relocated the vector. Hence we do another lookup here to
|
// in turn relocated the vector. Hence we do another lookup here to
|
||||||
// fixup the loans.
|
// fixup the loans.
|
||||||
match map[i] {
|
if return_loan {
|
||||||
Some((_, _, ref mut loans)) => { *loans = *loans - 1; }
|
match map[i] {
|
||||||
None => { libc::abort(); }
|
Some((_, _, ref mut loan)) => { *loan = NoLoan; }
|
||||||
|
None => { libc::abort(); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -269,9 +322,10 @@ pub unsafe fn local_set<T: 'static>(handle: Handle,
|
||||||
// First see if the map contains this key already
|
// First see if the map contains this key already
|
||||||
let curspot = map.iter().position(|entry| {
|
let curspot = map.iter().position(|entry| {
|
||||||
match *entry {
|
match *entry {
|
||||||
Some((ekey, _, loans)) if key == ekey => {
|
Some((ekey, _, loan)) if key == ekey => {
|
||||||
if loans != 0 {
|
if loan != NoLoan {
|
||||||
fail!("TLS value has been loaned via get already");
|
fail!("TLS value cannot be overwritten because it is
|
||||||
|
already borrowed as %s", loan.describe())
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -286,7 +340,7 @@ pub unsafe fn local_set<T: 'static>(handle: Handle,
|
||||||
}
|
}
|
||||||
|
|
||||||
match insertion_position(map, keyval) {
|
match insertion_position(map, keyval) {
|
||||||
Some(i) => { map[i] = Some((keyval, data, 0)); }
|
Some(i) => { map[i] = Some((keyval, data, NoLoan)); }
|
||||||
None => { map.push(Some((keyval, data, 0))); }
|
None => { map.push(Some((keyval, data, NoLoan))); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue