std: Second pass stabilization for thread_local
This commit performs a second pass over the `std::thread_local` module. Most of the functionality remains explicitly unstable, but the specific actions taken were: * `thread_local` is now stable * `thread_local!` is now stable * `thread_local::Key` is now stable * `thread_local::Key::with` is now stable * `thread_local::Key::destroyed` is deprecated in favor of a more general `state` function * `thread_local::Key::state` was added to query the three states that a key can be in: uninitialized, valid, or destroyed. This function, and the corresponding `State` enum, are both marked unstable as we may wish to expand it later on. * `thread_local::scoped` is entirely unstable. There hasn't been a whole lot of usage of this module in the standard distribution, so it remains unstable at this time. Note that while the structure `Key` is marked stable, it is currently forced to expose all of its implementation details due to the use of construction-via-macro. The use of construction-via-macro is currently required in order to place the `#[thread_local]` attribute on static in a platform-specific manner. These stability attributes were assigned assuming that it will be acceptable to tweak the implementation of `Key` in the future.
This commit is contained in:
parent
10d99a9734
commit
be11aa6d70
@ -10,9 +10,10 @@
|
||||
|
||||
use core::prelude::*;
|
||||
|
||||
use thread::Thread;
|
||||
use cell::RefCell;
|
||||
use string::String;
|
||||
use thread::Thread;
|
||||
use thread_local::State;
|
||||
|
||||
struct ThreadInfo {
|
||||
// This field holds the known bounds of the stack in (lo, hi)
|
||||
@ -27,7 +28,7 @@ thread_local! { static THREAD_INFO: RefCell<Option<ThreadInfo>> = RefCell::new(N
|
||||
|
||||
impl ThreadInfo {
|
||||
fn with<R>(f: |&mut ThreadInfo| -> R) -> R {
|
||||
if THREAD_INFO.destroyed() {
|
||||
if THREAD_INFO.state() == State::Destroyed {
|
||||
panic!("Use of std::thread::Thread::current() is not possible after \
|
||||
the thread's local data has been destroyed");
|
||||
}
|
||||
|
@ -35,20 +35,23 @@
|
||||
//! `Cell` or `RefCell` types.
|
||||
|
||||
#![macro_escape]
|
||||
#![experimental]
|
||||
#![stable]
|
||||
|
||||
use prelude::*;
|
||||
|
||||
use cell::UnsafeCell;
|
||||
|
||||
// Sure wish we had macro hygiene, no?
|
||||
#[doc(hidden)] pub use self::imp::Key as KeyInner;
|
||||
#[doc(hidden)] pub use self::imp::destroy_value;
|
||||
#[doc(hidden)] pub use sys_common::thread_local::INIT_INNER as OS_INIT_INNER;
|
||||
#[doc(hidden)] pub use sys_common::thread_local::StaticKey as OsStaticKey;
|
||||
|
||||
pub mod scoped;
|
||||
|
||||
// Sure wish we had macro hygiene, no?
|
||||
#[doc(hidden)]
|
||||
pub mod __impl {
|
||||
pub use super::imp::Key as KeyInner;
|
||||
pub use super::imp::destroy_value;
|
||||
pub use sys_common::thread_local::INIT_INNER as OS_INIT_INNER;
|
||||
pub use sys_common::thread_local::StaticKey as OsStaticKey;
|
||||
}
|
||||
|
||||
/// A thread local storage key which owns its contents.
|
||||
///
|
||||
/// This key uses the fastest possible implementation available to it for the
|
||||
@ -90,6 +93,7 @@ pub mod scoped;
|
||||
/// assert_eq!(*f.borrow(), 2);
|
||||
/// });
|
||||
/// ```
|
||||
#[stable]
|
||||
pub struct Key<T> {
|
||||
// The key itself may be tagged with #[thread_local], and this `Key` is
|
||||
// stored as a `static`, and it's not valid for a static to reference the
|
||||
@ -100,7 +104,7 @@ pub struct Key<T> {
|
||||
// This is trivially devirtualizable by LLVM because we never store anything
|
||||
// to this field and rustc can declare the `static` as constant as well.
|
||||
#[doc(hidden)]
|
||||
pub inner: fn() -> &'static KeyInner<UnsafeCell<Option<T>>>,
|
||||
pub inner: fn() -> &'static __impl::KeyInner<UnsafeCell<Option<T>>>,
|
||||
|
||||
// initialization routine to invoke to create a value
|
||||
#[doc(hidden)]
|
||||
@ -109,12 +113,12 @@ pub struct Key<T> {
|
||||
|
||||
/// Declare a new thread local storage key of type `std::thread_local::Key`.
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
#[stable]
|
||||
macro_rules! thread_local {
|
||||
(static $name:ident: $t:ty = $init:expr) => (
|
||||
static $name: ::std::thread_local::Key<$t> = {
|
||||
use std::cell::UnsafeCell as __UnsafeCell;
|
||||
use std::thread_local::KeyInner as __KeyInner;
|
||||
use std::thread_local::__impl::KeyInner as __KeyInner;
|
||||
use std::option::Option as __Option;
|
||||
use std::option::Option::None as __None;
|
||||
|
||||
@ -131,7 +135,7 @@ macro_rules! thread_local {
|
||||
(pub static $name:ident: $t:ty = $init:expr) => (
|
||||
pub static $name: ::std::thread_local::Key<$t> = {
|
||||
use std::cell::UnsafeCell as __UnsafeCell;
|
||||
use std::thread_local::KeyInner as __KeyInner;
|
||||
use std::thread_local::__impl::KeyInner as __KeyInner;
|
||||
use std::option::Option as __Option;
|
||||
use std::option::Option::None as __None;
|
||||
|
||||
@ -168,21 +172,22 @@ macro_rules! thread_local {
|
||||
// itself. Woohoo.
|
||||
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __thread_local_inner {
|
||||
(static $name:ident: $t:ty = $init:expr) => (
|
||||
#[cfg_attr(any(target_os = "macos", target_os = "linux"), thread_local)]
|
||||
static $name: ::std::thread_local::KeyInner<$t> =
|
||||
static $name: ::std::thread_local::__impl::KeyInner<$t> =
|
||||
__thread_local_inner!($init, $t);
|
||||
);
|
||||
(pub static $name:ident: $t:ty = $init:expr) => (
|
||||
#[cfg_attr(any(target_os = "macos", target_os = "linux"), thread_local)]
|
||||
pub static $name: ::std::thread_local::KeyInner<$t> =
|
||||
pub static $name: ::std::thread_local::__impl::KeyInner<$t> =
|
||||
__thread_local_inner!($init, $t);
|
||||
);
|
||||
($init:expr, $t:ty) => ({
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
const INIT: ::std::thread_local::KeyInner<$t> = {
|
||||
::std::thread_local::KeyInner {
|
||||
const _INIT: ::std::thread_local::__impl::KeyInner<$t> = {
|
||||
::std::thread_local::__impl::KeyInner {
|
||||
inner: ::std::cell::UnsafeCell { value: $init },
|
||||
dtor_registered: ::std::cell::UnsafeCell { value: false },
|
||||
dtor_running: ::std::cell::UnsafeCell { value: false },
|
||||
@ -190,24 +195,54 @@ macro_rules! __thread_local_inner {
|
||||
};
|
||||
|
||||
#[cfg(all(not(any(target_os = "macos", target_os = "linux"))))]
|
||||
const INIT: ::std::thread_local::KeyInner<$t> = {
|
||||
const _INIT: ::std::thread_local::__impl::KeyInner<$t> = {
|
||||
unsafe extern fn __destroy(ptr: *mut u8) {
|
||||
::std::thread_local::destroy_value::<$t>(ptr);
|
||||
::std::thread_local::__impl::destroy_value::<$t>(ptr);
|
||||
}
|
||||
|
||||
::std::thread_local::KeyInner {
|
||||
::std::thread_local::__impl::KeyInner {
|
||||
inner: ::std::cell::UnsafeCell { value: $init },
|
||||
os: ::std::thread_local::OsStaticKey {
|
||||
inner: ::std::thread_local::OS_INIT_INNER,
|
||||
os: ::std::thread_local::__impl::OsStaticKey {
|
||||
inner: ::std::thread_local::__impl::OS_INIT_INNER,
|
||||
dtor: ::std::option::Option::Some(__destroy as unsafe extern fn(*mut u8)),
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
INIT
|
||||
_INIT
|
||||
});
|
||||
}
|
||||
|
||||
/// Indicator of the state of a thread local storage key.
|
||||
#[unstable = "state querying was recently added"]
|
||||
#[deriving(Eq, PartialEq, Copy)]
|
||||
pub enum State {
|
||||
/// All keys are in this state whenever a thread starts. Keys will
|
||||
/// transition to the `Valid` state once the first call to `with` happens
|
||||
/// and the initialization expression succeeds.
|
||||
///
|
||||
/// Keys in the `Uninitialized` state will yield a reference to the closure
|
||||
/// passed to `with` so long as the initialization routine does not panic.
|
||||
Uninitialized,
|
||||
|
||||
/// Once a key has been accessed successfully, it will enter the `Valid`
|
||||
/// state. Keys in the `Valid` state will remain so until the thread exits,
|
||||
/// at which point the destructor will be run and the key will enter the
|
||||
/// `Destroyed` state.
|
||||
///
|
||||
/// Keys in the `Valid` state will be guaranteed to yield a reference to the
|
||||
/// closure passed to `with`.
|
||||
Valid,
|
||||
|
||||
/// When a thread exits, the destructors for keys will be run (if
|
||||
/// necessary). While a destructor is running, and possibly after a
|
||||
/// destructor has run, a key is in the `Destroyed` state.
|
||||
///
|
||||
/// Keys in the `Destroyed` states will trigger a panic when accessed via
|
||||
/// `with`.
|
||||
Destroyed,
|
||||
}
|
||||
|
||||
impl<T: 'static> Key<T> {
|
||||
/// Acquire a reference to the value in this TLS key.
|
||||
///
|
||||
@ -219,6 +254,7 @@ impl<T: 'static> Key<T> {
|
||||
/// This function will `panic!()` if the key currently has its
|
||||
/// destructor running, and it **may** panic if the destructor has
|
||||
/// previously been run for this thread.
|
||||
#[stable]
|
||||
pub fn with<F, R>(&'static self, f: F) -> R
|
||||
where F: FnOnce(&T) -> R {
|
||||
let slot = (self.inner)();
|
||||
@ -233,17 +269,52 @@ impl<T: 'static> Key<T> {
|
||||
}
|
||||
|
||||
unsafe fn init(&self, slot: &UnsafeCell<Option<T>>) -> &T {
|
||||
*slot.get() = Some((self.init)());
|
||||
(*slot.get()).as_ref().unwrap()
|
||||
// Execute the initialization up front, *then* move it into our slot,
|
||||
// just in case initialization fails.
|
||||
let value = (self.init)();
|
||||
let ptr = slot.get();
|
||||
*ptr = Some(value);
|
||||
(*ptr).as_ref().unwrap()
|
||||
}
|
||||
|
||||
/// Test this TLS key to determine whether its value has been destroyed for
|
||||
/// the current thread or not.
|
||||
/// Query the current state of this key.
|
||||
///
|
||||
/// This will not initialize the key if it is not already initialized.
|
||||
pub fn destroyed(&'static self) -> bool {
|
||||
unsafe { (self.inner)().get().is_none() }
|
||||
/// A key is initially in the `Uninitialized` state whenever a thread
|
||||
/// starts. It will remain in this state up until the first call to `with`
|
||||
/// within a thread has run the initialization expression successfully.
|
||||
///
|
||||
/// Once the initialization expression succeeds, the key transitions to the
|
||||
/// `Valid` state which will guarantee that future calls to `with` will
|
||||
/// succeed within the thread.
|
||||
///
|
||||
/// When a thread exits, each key will be destroyed in turn, and as keys are
|
||||
/// destroyed they will enter the `Destroyed` state just before the
|
||||
/// destructor starts to run. Keys may remain in the `Destroyed` state after
|
||||
/// destruction has completed. Keys without destructors (e.g. with types
|
||||
/// that are `Copy`), may never enter the `Destroyed` state.
|
||||
///
|
||||
/// Keys in the `Uninitialized` can be accessed so long as the
|
||||
/// initialization does not panic. Keys in the `Valid` state are guaranteed
|
||||
/// to be able to be accessed. Keys in the `Destroyed` state will panic on
|
||||
/// any call to `with`.
|
||||
#[unstable = "state querying was recently added"]
|
||||
pub fn state(&'static self) -> State {
|
||||
unsafe {
|
||||
match (self.inner)().get() {
|
||||
Some(cell) => {
|
||||
match *cell.get() {
|
||||
Some(..) => State::Valid,
|
||||
None => State::Uninitialized,
|
||||
}
|
||||
}
|
||||
None => State::Destroyed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deprecated
|
||||
#[deprecated = "function renamed to state() and returns more info"]
|
||||
pub fn destroyed(&'static self) -> bool { self.state() == State::Destroyed }
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
@ -456,6 +527,7 @@ mod tests {
|
||||
use prelude::*;
|
||||
|
||||
use cell::UnsafeCell;
|
||||
use super::State;
|
||||
use thread::Thread;
|
||||
|
||||
struct Foo(Sender<()>);
|
||||
@ -489,6 +561,29 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn states() {
|
||||
struct Foo;
|
||||
impl Drop for Foo {
|
||||
fn drop(&mut self) {
|
||||
assert!(FOO.state() == State::Destroyed);
|
||||
}
|
||||
}
|
||||
fn foo() -> Foo {
|
||||
assert!(FOO.state() == State::Uninitialized);
|
||||
Foo
|
||||
}
|
||||
thread_local!(static FOO: Foo = foo());
|
||||
|
||||
Thread::spawn(|| {
|
||||
assert!(FOO.state() == State::Uninitialized);
|
||||
FOO.with(|_| {
|
||||
assert!(FOO.state() == State::Valid);
|
||||
});
|
||||
assert!(FOO.state() == State::Valid);
|
||||
}).join().ok().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_dtor() {
|
||||
thread_local!(static FOO: UnsafeCell<Option<Foo>> = UnsafeCell {
|
||||
@ -521,7 +616,7 @@ mod tests {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
HITS += 1;
|
||||
if K2.destroyed() {
|
||||
if K2.state() == State::Destroyed {
|
||||
assert_eq!(HITS, 3);
|
||||
} else {
|
||||
if HITS == 1 {
|
||||
@ -537,7 +632,7 @@ mod tests {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
HITS += 1;
|
||||
assert!(!K1.destroyed());
|
||||
assert!(K1.state() != State::Destroyed);
|
||||
assert_eq!(HITS, 2);
|
||||
K1.with(|s| *s.get() = Some(S1));
|
||||
}
|
||||
@ -558,7 +653,7 @@ mod tests {
|
||||
|
||||
impl Drop for S1 {
|
||||
fn drop(&mut self) {
|
||||
assert!(K1.destroyed());
|
||||
assert!(K1.state() == State::Destroyed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -581,7 +676,7 @@ mod tests {
|
||||
fn drop(&mut self) {
|
||||
let S1(ref tx) = *self;
|
||||
unsafe {
|
||||
if !K2.destroyed() {
|
||||
if K2.state() != State::Destroyed {
|
||||
K2.with(|s| *s.get() = Some(Foo(tx.clone())));
|
||||
}
|
||||
}
|
||||
|
@ -39,12 +39,17 @@
|
||||
//! ```
|
||||
|
||||
#![macro_escape]
|
||||
#![unstable = "scoped TLS has yet to have wide enough use to fully consider \
|
||||
stabilizing its interface"]
|
||||
|
||||
use prelude::*;
|
||||
|
||||
// macro hygiene sure would be nice, wouldn't it?
|
||||
#[doc(hidden)] pub use self::imp::KeyInner;
|
||||
#[doc(hidden)] pub use sys_common::thread_local::INIT as OS_INIT;
|
||||
#[doc(hidden)]
|
||||
pub mod __impl {
|
||||
pub use super::imp::KeyInner;
|
||||
pub use sys_common::thread_local::INIT as OS_INIT;
|
||||
}
|
||||
|
||||
/// Type representing a thread local storage key corresponding to a reference
|
||||
/// to the type parameter `T`.
|
||||
@ -53,7 +58,7 @@ use prelude::*;
|
||||
/// type `T` scoped to a particular lifetime. Keys provides two methods, `set`
|
||||
/// and `with`, both of which currently use closures to control the scope of
|
||||
/// their contents.
|
||||
pub struct Key<T> { #[doc(hidden)] pub inner: KeyInner<T> }
|
||||
pub struct Key<T> { #[doc(hidden)] pub inner: __impl::KeyInner<T> }
|
||||
|
||||
/// Declare a new scoped thread local storage key.
|
||||
///
|
||||
@ -88,21 +93,21 @@ macro_rules! __scoped_thread_local_inner {
|
||||
use std::thread_local::scoped::Key as __Key;
|
||||
|
||||
#[cfg(not(any(windows, target_os = "android", target_os = "ios")))]
|
||||
const INIT: __Key<$t> = __Key {
|
||||
inner: ::std::thread_local::scoped::KeyInner {
|
||||
const _INIT: __Key<$t> = __Key {
|
||||
inner: ::std::thread_local::scoped::__impl::KeyInner {
|
||||
inner: ::std::cell::UnsafeCell { value: 0 as *mut _ },
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(any(windows, target_os = "android", target_os = "ios"))]
|
||||
const INIT: __Key<$t> = __Key {
|
||||
inner: ::std::thread_local::scoped::KeyInner {
|
||||
inner: ::std::thread_local::scoped::OS_INIT,
|
||||
const _INIT: __Key<$t> = __Key {
|
||||
inner: ::std::thread_local::scoped::__impl::KeyInner {
|
||||
inner: ::std::thread_local::scoped::__impl::OS_INIT,
|
||||
marker: ::std::kinds::marker::InvariantType,
|
||||
}
|
||||
};
|
||||
|
||||
INIT
|
||||
_INIT
|
||||
})
|
||||
}
|
||||
|
||||
@ -139,7 +144,7 @@ impl<T> Key<T> {
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
struct Reset<'a, T: 'a> {
|
||||
key: &'a KeyInner<T>,
|
||||
key: &'a __impl::KeyInner<T>,
|
||||
val: *mut T,
|
||||
}
|
||||
#[unsafe_destructor]
|
||||
|
Loading…
x
Reference in New Issue
Block a user