Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9dcaa15

Browse files
committedJun 25, 2024
std: separate TLS key creation from TLS access
Currently, `std` performs an atomic load to get the OS key on every access to `StaticKey` even when the key is already known. This PR thus replaces `StaticKey` with the platform-specific `get` and `set` function and a new `LazyKey` type that acts as a `LazyLock<Key>`, allowing the reuse of the retreived key for multiple accesses.
1 parent a451b2a commit 9dcaa15

File tree

8 files changed

+100
-125
lines changed

8 files changed

+100
-125
lines changed
 

‎std/src/sys/thread_local/guard/key.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
55
use crate::ptr;
66
use crate::sys::thread_local::destructors;
7-
use crate::sys::thread_local::key::StaticKey;
7+
use crate::sys::thread_local::key::{set, LazyKey};
88

99
pub fn enable() {
10-
static DTORS: StaticKey = StaticKey::new(Some(run));
10+
static DTORS: LazyKey = LazyKey::new(Some(run));
1111

1212
// Setting the key value to something other than NULL will result in the
1313
// destructor being run at thread exit.
1414
unsafe {
15-
DTORS.set(ptr::without_provenance_mut(1));
15+
set(DTORS.force(), ptr::without_provenance_mut(1));
1616
}
1717

1818
unsafe extern "C" fn run(_: *mut u8) {

‎std/src/sys/thread_local/key/racy.rs

+8-48
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! A `StaticKey` implementation using racy initialization.
1+
//! A `LazyKey` implementation using racy initialization.
22
//!
33
//! Unfortunately, none of the platforms currently supported by `std` allows
44
//! creating TLS keys at compile-time. Thus we need a way to lazily create keys.
@@ -10,34 +10,12 @@ use crate::sync::atomic::{self, AtomicUsize, Ordering};
1010

1111
/// A type for TLS keys that are statically allocated.
1212
///
13-
/// This type is entirely `unsafe` to use as it does not protect against
14-
/// use-after-deallocation or use-during-deallocation.
15-
///
16-
/// The actual OS-TLS key is lazily allocated when this is used for the first
17-
/// time. The key is also deallocated when the Rust runtime exits or `destroy`
18-
/// is called, whichever comes first.
19-
///
20-
/// # Examples
21-
///
22-
/// ```ignore (cannot-doctest-private-modules)
23-
/// use tls::os::{StaticKey, INIT};
24-
///
25-
/// // Use a regular global static to store the key.
26-
/// static KEY: StaticKey = INIT;
27-
///
28-
/// // The state provided via `get` and `set` is thread-local.
29-
/// unsafe {
30-
/// assert!(KEY.get().is_null());
31-
/// KEY.set(1 as *mut u8);
32-
/// }
33-
/// ```
34-
pub struct StaticKey {
13+
/// This is basically a `LazyLock<Key>`, but avoids blocking and circular
14+
/// dependencies with the rest of `std`.
15+
pub struct LazyKey {
3516
/// Inner static TLS key (internals).
3617
key: AtomicUsize,
3718
/// Destructor for the TLS value.
38-
///
39-
/// See `Key::new` for information about when the destructor runs and how
40-
/// it runs.
4119
dtor: Option<unsafe extern "C" fn(*mut u8)>,
4220
}
4321

@@ -51,32 +29,14 @@ const KEY_SENTVAL: usize = 0;
5129
#[cfg(target_os = "nto")]
5230
const KEY_SENTVAL: usize = libc::PTHREAD_KEYS_MAX + 1;
5331

54-
impl StaticKey {
32+
impl LazyKey {
5533
#[rustc_const_unstable(feature = "thread_local_internals", issue = "none")]
56-
pub const fn new(dtor: Option<unsafe extern "C" fn(*mut u8)>) -> StaticKey {
57-
StaticKey { key: atomic::AtomicUsize::new(KEY_SENTVAL), dtor }
58-
}
59-
60-
/// Gets the value associated with this TLS key
61-
///
62-
/// This will lazily allocate a TLS key from the OS if one has not already
63-
/// been allocated.
64-
#[inline]
65-
pub unsafe fn get(&self) -> *mut u8 {
66-
unsafe { super::get(self.key()) }
67-
}
68-
69-
/// Sets this TLS key to a new value.
70-
///
71-
/// This will lazily allocate a TLS key from the OS if one has not already
72-
/// been allocated.
73-
#[inline]
74-
pub unsafe fn set(&self, val: *mut u8) {
75-
unsafe { super::set(self.key(), val) }
34+
pub const fn new(dtor: Option<unsafe extern "C" fn(*mut u8)>) -> LazyKey {
35+
LazyKey { key: atomic::AtomicUsize::new(KEY_SENTVAL), dtor }
7636
}
7737

7838
#[inline]
79-
fn key(&self) -> super::Key {
39+
pub fn force(&self) -> super::Key {
8040
match self.key.load(Ordering::Acquire) {
8141
KEY_SENTVAL => self.lazy_init() as super::Key,
8242
n => n as super::Key,

‎std/src/sys/thread_local/key/tests.rs

+24-15
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
1-
use super::StaticKey;
1+
use super::{get, set, LazyKey};
22
use crate::ptr;
33

44
#[test]
55
fn smoke() {
6-
static K1: StaticKey = StaticKey::new(None);
7-
static K2: StaticKey = StaticKey::new(None);
6+
static K1: LazyKey = LazyKey::new(None);
7+
static K2: LazyKey = LazyKey::new(None);
8+
9+
let k1 = K1.force();
10+
let k2 = K2.force();
11+
assert_ne!(k1, k2);
12+
13+
assert_eq!(K1.force(), k1);
14+
assert_eq!(K2.force(), k2);
815

916
unsafe {
10-
assert!(K1.get().is_null());
11-
assert!(K2.get().is_null());
12-
K1.set(ptr::without_provenance_mut(1));
13-
K2.set(ptr::without_provenance_mut(2));
14-
assert_eq!(K1.get() as usize, 1);
15-
assert_eq!(K2.get() as usize, 2);
17+
assert!(get(k1).is_null());
18+
assert!(get(k2).is_null());
19+
set(k1, ptr::without_provenance_mut(1));
20+
set(k2, ptr::without_provenance_mut(2));
21+
assert_eq!(get(k1) as usize, 1);
22+
assert_eq!(get(k2) as usize, 2);
1623
}
1724
}
1825

@@ -26,25 +33,27 @@ fn destructors() {
2633
drop(unsafe { Arc::from_raw(ptr as *const ()) });
2734
}
2835

29-
static KEY: StaticKey = StaticKey::new(Some(destruct));
36+
static KEY: LazyKey = LazyKey::new(Some(destruct));
3037

3138
let shared1 = Arc::new(());
3239
let shared2 = Arc::clone(&shared1);
3340

41+
let key = KEY.force();
3442
unsafe {
35-
assert!(KEY.get().is_null());
36-
KEY.set(Arc::into_raw(shared1) as *mut u8);
43+
assert!(get(key).is_null());
44+
set(key, Arc::into_raw(shared1) as *mut u8);
3745
}
3846

3947
thread::spawn(move || unsafe {
40-
assert!(KEY.get().is_null());
41-
KEY.set(Arc::into_raw(shared2) as *mut u8);
48+
let key = KEY.force();
49+
assert!(get(key).is_null());
50+
set(key, Arc::into_raw(shared2) as *mut u8);
4251
})
4352
.join()
4453
.unwrap();
4554

4655
// Leak the Arc, let the TLS destructor clean it up.
47-
let shared1 = unsafe { ManuallyDrop::new(Arc::from_raw(KEY.get() as *const ())) };
56+
let shared1 = unsafe { ManuallyDrop::new(Arc::from_raw(get(key) as *const ())) };
4857
assert_eq!(
4958
Arc::strong_count(&shared1),
5059
1,

‎std/src/sys/thread_local/key/unix.rs

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub unsafe fn set(key: Key, value: *mut u8) {
1616
}
1717

1818
#[inline]
19+
#[cfg(any(not(target_thread_local), test))]
1920
pub unsafe fn get(key: Key) -> *mut u8 {
2021
unsafe { libc::pthread_getspecific(key) as *mut u8 }
2122
}

‎std/src/sys/thread_local/key/windows.rs

+28-28
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Implementation of `StaticKey` for Windows.
1+
//! Implementation of `LazyKey` for Windows.
22
//!
33
//! Windows has no native support for running destructors so we manage our own
44
//! list of destructors to keep track of how to destroy keys. We then install a
@@ -13,9 +13,9 @@
1313
//! don't reach a fixed point after a short while then we just inevitably leak
1414
//! something.
1515
//!
16-
//! The list is implemented as an atomic single-linked list of `StaticKey`s and
16+
//! The list is implemented as an atomic single-linked list of `LazyKey`s and
1717
//! does not support unregistration. Unfortunately, this means that we cannot
18-
//! use racy initialization for creating the keys in `StaticKey`, as that could
18+
//! use racy initialization for creating the keys in `LazyKey`, as that could
1919
//! result in destructors being missed. Hence, we synchronize the creation of
2020
//! keys with destructors through [`INIT_ONCE`](c::INIT_ONCE) (`std`'s
2121
//! [`Once`](crate::sync::Once) cannot be used since it might use TLS itself).
@@ -33,26 +33,26 @@ use crate::sync::atomic::{
3333
use crate::sys::c;
3434
use crate::sys::thread_local::guard;
3535

36-
type Key = c::DWORD;
36+
pub type Key = c::DWORD;
3737
type Dtor = unsafe extern "C" fn(*mut u8);
3838

39-
pub struct StaticKey {
39+
pub struct LazyKey {
4040
/// The key value shifted up by one. Since TLS_OUT_OF_INDEXES == DWORD::MAX
4141
/// is not a valid key value, this allows us to use zero as sentinel value
4242
/// without risking overflow.
4343
key: AtomicU32,
4444
dtor: Option<Dtor>,
45-
next: AtomicPtr<StaticKey>,
45+
next: AtomicPtr<LazyKey>,
4646
/// Currently, destructors cannot be unregistered, so we cannot use racy
4747
/// initialization for keys. Instead, we need synchronize initialization.
4848
/// Use the Windows-provided `Once` since it does not require TLS.
4949
once: UnsafeCell<c::INIT_ONCE>,
5050
}
5151

52-
impl StaticKey {
52+
impl LazyKey {
5353
#[inline]
54-
pub const fn new(dtor: Option<Dtor>) -> StaticKey {
55-
StaticKey {
54+
pub const fn new(dtor: Option<Dtor>) -> LazyKey {
55+
LazyKey {
5656
key: AtomicU32::new(0),
5757
dtor,
5858
next: AtomicPtr::new(ptr::null_mut()),
@@ -61,18 +61,7 @@ impl StaticKey {
6161
}
6262

6363
#[inline]
64-
pub unsafe fn set(&'static self, val: *mut u8) {
65-
let r = unsafe { c::TlsSetValue(self.key(), val.cast()) };
66-
debug_assert_eq!(r, c::TRUE);
67-
}
68-
69-
#[inline]
70-
pub unsafe fn get(&'static self) -> *mut u8 {
71-
unsafe { c::TlsGetValue(self.key()).cast() }
72-
}
73-
74-
#[inline]
75-
fn key(&'static self) -> Key {
64+
pub fn force(&'static self) -> Key {
7665
match self.key.load(Acquire) {
7766
0 => unsafe { self.init() },
7867
key => key - 1,
@@ -141,17 +130,28 @@ impl StaticKey {
141130
}
142131
}
143132

144-
unsafe impl Send for StaticKey {}
145-
unsafe impl Sync for StaticKey {}
133+
unsafe impl Send for LazyKey {}
134+
unsafe impl Sync for LazyKey {}
135+
136+
#[inline]
137+
pub unsafe fn set(key: Key, val: *mut u8) {
138+
let r = unsafe { c::TlsSetValue(key, val.cast()) };
139+
debug_assert_eq!(r, c::TRUE);
140+
}
141+
142+
#[inline]
143+
pub unsafe fn get(key: Key) -> *mut u8 {
144+
unsafe { c::TlsGetValue(key).cast() }
145+
}
146146

147-
static DTORS: AtomicPtr<StaticKey> = AtomicPtr::new(ptr::null_mut());
147+
static DTORS: AtomicPtr<LazyKey> = AtomicPtr::new(ptr::null_mut());
148148

149149
/// Should only be called once per key, otherwise loops or breaks may occur in
150150
/// the linked list.
151-
unsafe fn register_dtor(key: &'static StaticKey) {
151+
unsafe fn register_dtor(key: &'static LazyKey) {
152152
guard::enable();
153153

154-
let this = <*const StaticKey>::cast_mut(key);
154+
let this = <*const LazyKey>::cast_mut(key);
155155
// Use acquire ordering to pass along the changes done by the previously
156156
// registered keys when we store the new head with release ordering.
157157
let mut head = DTORS.load(Acquire);
@@ -176,9 +176,9 @@ pub unsafe fn run_dtors() {
176176
let dtor = unsafe { (*cur).dtor.unwrap() };
177177
cur = unsafe { (*cur).next.load(Relaxed) };
178178

179-
// In StaticKey::init, we register the dtor before setting `key`.
179+
// In LazyKey::init, we register the dtor before setting `key`.
180180
// So if one thread's `run_dtors` races with another thread executing `init` on the same
181-
// `StaticKey`, we can encounter a key of 0 here. That means this key was never
181+
// `LazyKey`, we can encounter a key of 0 here. That means this key was never
182182
// initialized in this thread so we can safely skip it.
183183
if pre_key == 0 {
184184
continue;

‎std/src/sys/thread_local/key/xous.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
//! really.
3131
//!
3232
//! Perhaps one day we can fold the `Box` here into a static allocation,
33-
//! expanding the `StaticKey` structure to contain not only a slot for the TLS
33+
//! expanding the `LazyKey` structure to contain not only a slot for the TLS
3434
//! key but also a slot for the destructor queue on windows. An optimization for
3535
//! another day!
3636

‎std/src/sys/thread_local/mod.rs

+13-8
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ cfg_if::cfg_if! {
3636
pub use native::{EagerStorage, LazyStorage, thread_local_inner};
3737
} else {
3838
mod os;
39-
pub use os::{Key, thread_local_inner};
39+
pub use os::{Storage, thread_local_inner};
4040
}
4141
}
4242

@@ -126,28 +126,33 @@ pub(crate) mod key {
126126
mod unix;
127127
#[cfg(test)]
128128
mod tests;
129-
pub(super) use racy::StaticKey;
130-
use unix::{Key, create, destroy, get, set};
129+
pub(super) use racy::LazyKey;
130+
pub(super) use unix::{Key, set};
131+
#[cfg(any(not(target_thread_local), test))]
132+
pub(super) use unix::get;
133+
use unix::{create, destroy};
131134
} else if #[cfg(all(not(target_thread_local), target_os = "windows"))] {
132135
#[cfg(test)]
133136
mod tests;
134137
mod windows;
135-
pub(super) use windows::{StaticKey, run_dtors};
138+
pub(super) use windows::{Key, LazyKey, get, run_dtors, set};
136139
} else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] {
137140
mod racy;
138141
mod sgx;
139142
#[cfg(test)]
140143
mod tests;
141-
pub(super) use racy::StaticKey;
142-
use sgx::{Key, create, destroy, get, set};
144+
pub(super) use racy::LazyKey;
145+
pub(super) use sgx::{Key, get, set};
146+
use sgx::{create, destroy};
143147
} else if #[cfg(target_os = "xous")] {
144148
mod racy;
145149
#[cfg(test)]
146150
mod tests;
147151
mod xous;
148-
pub(super) use racy::StaticKey;
152+
pub(super) use racy::LazyKey;
149153
pub(crate) use xous::destroy_tls;
150-
use xous::{Key, create, destroy, get, set};
154+
pub(super) use xous::{Key, get, set};
155+
use xous::{create, destroy};
151156
}
152157
}
153158
}
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.