Skip to content

Stabilize LocalKey::try_with #48585

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from Mar 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 22 additions & 20 deletions src/libstd/io/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use io::{self, Initializer, BufReader, LineWriter};
use sync::{Arc, Mutex, MutexGuard};
use sys::stdio;
use sys_common::remutex::{ReentrantMutex, ReentrantMutexGuard};
use thread::{LocalKey, LocalKeyState};
use thread::LocalKey;

/// Stdout used by print! and println! macros
thread_local! {
Expand Down Expand Up @@ -663,29 +663,31 @@ pub fn set_print(sink: Option<Box<Write + Send>>) -> Option<Box<Write + Send>> {
///
/// This function is used to print error messages, so it takes extra
/// care to avoid causing a panic when `local_stream` is unusable.
/// For instance, if the TLS key for the local stream is uninitialized
/// or already destroyed, or if the local stream is locked by another
/// For instance, if the TLS key for the local stream is
/// already destroyed, or if the local stream is locked by another
/// thread, it will just fall back to the global stream.
///
/// However, if the actual I/O causes an error, this function does panic.
fn print_to<T>(args: fmt::Arguments,
local_s: &'static LocalKey<RefCell<Option<Box<Write+Send>>>>,
global_s: fn() -> T,
label: &str) where T: Write {
let result = match local_s.state() {
LocalKeyState::Uninitialized |
LocalKeyState::Destroyed => global_s().write_fmt(args),
LocalKeyState::Valid => {
local_s.with(|s| {
if let Ok(mut borrowed) = s.try_borrow_mut() {
if let Some(w) = borrowed.as_mut() {
return w.write_fmt(args);
}
}
global_s().write_fmt(args)
})
fn print_to<T>(
args: fmt::Arguments,
local_s: &'static LocalKey<RefCell<Option<Box<Write+Send>>>>,
global_s: fn() -> T,
label: &str,
)
where
T: Write,
{
let result = local_s.try_with(|s| {
if let Ok(mut borrowed) = s.try_borrow_mut() {
if let Some(w) = borrowed.as_mut() {
return w.write_fmt(args);
}
}
};
global_s().write_fmt(args)
}).unwrap_or_else(|_| {
global_s().write_fmt(args)
});

if let Err(e) = result {
panic!("failed printing to {}: {}", label, e);
}
Expand Down
128 changes: 15 additions & 113 deletions src/libstd/thread/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,64 +195,20 @@ macro_rules! __thread_local_inner {
}
}

/// Indicator of the state of a thread local storage key.
#[unstable(feature = "thread_local_state",
reason = "state querying was recently added",
issue = "27716")]
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum LocalKeyState {
/// 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.
///
/// [`with`]: ../../std/thread/struct.LocalKey.html#method.with
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`].
///
/// [`with`]: ../../std/thread/struct.LocalKey.html#method.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`].
///
/// [`with`]: ../../std/thread/struct.LocalKey.html#method.with
Destroyed,
}

/// An error returned by [`LocalKey::try_with`](struct.LocalKey.html#method.try_with).
#[unstable(feature = "thread_local_state",
reason = "state querying was recently added",
issue = "27716")]
#[stable(feature = "thread_local_try_with", since = "1.26.0")]
pub struct AccessError {
_private: (),
}

#[unstable(feature = "thread_local_state",
reason = "state querying was recently added",
issue = "27716")]
#[stable(feature = "thread_local_try_with", since = "1.26.0")]
impl fmt::Debug for AccessError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("AccessError").finish()
}
}

#[unstable(feature = "thread_local_state",
reason = "state querying was recently added",
issue = "27716")]
#[stable(feature = "thread_local_try_with", since = "1.26.0")]
impl fmt::Display for AccessError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt("already destroyed", f)
Expand Down Expand Up @@ -312,64 +268,21 @@ impl<T: 'static> LocalKey<T> {
(*ptr).as_ref().unwrap()
}

/// Query the current state of this key.
///
/// 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. Some keys might skip the `Uninitialized`
/// state altogether and start in the `Valid` state as an optimization
/// (e.g. keys initialized with a constant expression), but no guarantees
/// are made.
///
/// 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` state 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`].
///
/// [`with`]: ../../std/thread/struct.LocalKey.html#method.with
/// [`Copy`]: ../../std/marker/trait.Copy.html
#[unstable(feature = "thread_local_state",
reason = "state querying was recently added",
issue = "27716")]
pub fn state(&'static self) -> LocalKeyState {
unsafe {
match (self.inner)() {
Some(cell) => {
match *cell.get() {
Some(..) => LocalKeyState::Valid,
None => LocalKeyState::Uninitialized,
}
}
None => LocalKeyState::Destroyed,
}
}
}

/// Acquires a reference to the value in this TLS key.
///
/// This will lazily initialize the value if this thread has not referenced
/// this key yet. If the key has been destroyed (which may happen if this is called
/// in a destructor), this function will return a ThreadLocalError.
/// in a destructor), this function will return a `ThreadLocalError`.
///
/// # Panics
///
/// This function will still `panic!()` if the key is uninitialized and the
/// key's initializer panics.
#[unstable(feature = "thread_local_state",
reason = "state querying was recently added",
issue = "27716")]
#[stable(feature = "thread_local_try_with", since = "1.26.0")]
pub fn try_with<F, R>(&'static self, f: F) -> Result<R, AccessError>
where F: FnOnce(&T) -> R {
where
F: FnOnce(&T) -> R,
{
unsafe {
let slot = (self.inner)().ok_or(AccessError {
_private: (),
Expand Down Expand Up @@ -530,7 +443,6 @@ pub mod os {
mod tests {
use sync::mpsc::{channel, Sender};
use cell::{Cell, UnsafeCell};
use super::LocalKeyState;
use thread;

struct Foo(Sender<()>);
Expand Down Expand Up @@ -569,21 +481,13 @@ mod tests {
struct Foo;
impl Drop for Foo {
fn drop(&mut self) {
assert!(FOO.state() == LocalKeyState::Destroyed);
assert!(FOO.try_with(|_| ()).is_err());
}
}
fn foo() -> Foo {
assert!(FOO.state() == LocalKeyState::Uninitialized);
Foo
}
thread_local!(static FOO: Foo = foo());
thread_local!(static FOO: Foo = Foo);

thread::spawn(|| {
assert!(FOO.state() == LocalKeyState::Uninitialized);
FOO.with(|_| {
assert!(FOO.state() == LocalKeyState::Valid);
});
assert!(FOO.state() == LocalKeyState::Valid);
assert!(FOO.try_with(|_| ()).is_ok());
}).join().ok().unwrap();
}

Expand Down Expand Up @@ -613,7 +517,7 @@ mod tests {
fn drop(&mut self) {
unsafe {
HITS += 1;
if K2.state() == LocalKeyState::Destroyed {
if K2.try_with(|_| ()).is_err() {
assert_eq!(HITS, 3);
} else {
if HITS == 1 {
Expand All @@ -629,7 +533,7 @@ mod tests {
fn drop(&mut self) {
unsafe {
HITS += 1;
assert!(K1.state() != LocalKeyState::Destroyed);
assert!(K1.try_with(|_| ()).is_ok());
assert_eq!(HITS, 2);
K1.with(|s| *s.get() = Some(S1));
}
Expand All @@ -648,7 +552,7 @@ mod tests {

impl Drop for S1 {
fn drop(&mut self) {
assert!(K1.state() == LocalKeyState::Destroyed);
assert!(K1.try_with(|_| ()).is_err());
}
}

Expand All @@ -672,9 +576,7 @@ mod tests {
fn drop(&mut self) {
let S1(ref tx) = *self;
unsafe {
if K2.state() != LocalKeyState::Destroyed {
K2.with(|s| *s.get() = Some(Foo(tx.clone())));
}
let _ = K2.try_with(|s| *s.get() = Some(Foo(tx.clone())));
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/libstd/thread/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ use time::Duration;
#[macro_use] mod local;

#[stable(feature = "rust1", since = "1.0.0")]
pub use self::local::{LocalKey, LocalKeyState, AccessError};
pub use self::local::{LocalKey, AccessError};

// The types used by the thread_local! macro to access TLS keys. Note that there
// are two types, the "OS" type and the "fast" type. The OS thread local key
Expand Down
10 changes: 4 additions & 6 deletions src/test/run-pass/tls-init-on-init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@

// ignore-emscripten no threads support

#![feature(thread_local_state)]
#![feature(thread_local_try_with)]

use std::thread::{self, LocalKeyState};
use std::thread;
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};

struct Foo { cnt: usize }
Expand All @@ -37,10 +37,8 @@ impl Drop for Foo {
FOO.with(|foo| assert_eq!(foo.cnt, 0));
} else {
assert_eq!(self.cnt, 0);
match FOO.state() {
LocalKeyState::Valid => panic!("should not be in valid state"),
LocalKeyState::Uninitialized |
LocalKeyState::Destroyed => {}
if FOO.try_with(|_| ()).is_ok() {
panic!("should not be in valid state");
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/run-pass/tls-try-with.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

// ignore-emscripten no threads support

#![feature(thread_local_state)]
#![feature(thread_local_try_with)]

use std::thread;

Expand Down