Skip to content

Commit 852f15c

Browse files
committed
Auto merge of #141685 - orlp:inplace-tls-drop, r=joboet
Do not move thread-locals before dropping Fixes #140816. I also (potentially) improved the speed of `get_or_init` a bit by having an explicit hot/cold path. We still move the value before dropping in the event of a recursive initialization (leading to double-initialization with one value being silently dropped). This is the old behavior, but changing this to panic instead would involve changing tests and also the other OS-specific `thread_local/os.rs` implementation, which is more than I'd like in this PR.
2 parents 738c08b + b374adc commit 852f15c

File tree

2 files changed

+88
-29
lines changed

2 files changed

+88
-29
lines changed
Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use crate::cell::UnsafeCell;
2-
use crate::hint::unreachable_unchecked;
1+
use crate::cell::{Cell, UnsafeCell};
2+
use crate::mem::MaybeUninit;
33
use crate::ptr;
44
use crate::sys::thread_local::{abort_on_dtor_unwind, destructors};
55

6-
pub unsafe trait DestroyedState: Sized {
6+
pub unsafe trait DestroyedState: Sized + Copy {
77
fn register_dtor<T>(s: &Storage<T, Self>);
88
}
99

@@ -19,23 +19,28 @@ unsafe impl DestroyedState for () {
1919
}
2020
}
2121

22-
enum State<T, D> {
23-
Initial,
24-
Alive(T),
22+
#[derive(Copy, Clone)]
23+
enum State<D> {
24+
Uninitialized,
25+
Alive,
2526
Destroyed(D),
2627
}
2728

2829
#[allow(missing_debug_implementations)]
2930
pub struct Storage<T, D> {
30-
state: UnsafeCell<State<T, D>>,
31+
state: Cell<State<D>>,
32+
value: UnsafeCell<MaybeUninit<T>>,
3133
}
3234

3335
impl<T, D> Storage<T, D>
3436
where
3537
D: DestroyedState,
3638
{
3739
pub const fn new() -> Storage<T, D> {
38-
Storage { state: UnsafeCell::new(State::Initial) }
40+
Storage {
41+
state: Cell::new(State::Uninitialized),
42+
value: UnsafeCell::new(MaybeUninit::uninit()),
43+
}
3944
}
4045

4146
/// Gets a pointer to the TLS value, potentially initializing it with the
@@ -49,35 +54,49 @@ where
4954
/// The `self` reference must remain valid until the TLS destructor is run.
5055
#[inline]
5156
pub unsafe fn get_or_init(&self, i: Option<&mut Option<T>>, f: impl FnOnce() -> T) -> *const T {
52-
let state = unsafe { &*self.state.get() };
53-
match state {
54-
State::Alive(v) => v,
55-
State::Destroyed(_) => ptr::null(),
56-
State::Initial => unsafe { self.initialize(i, f) },
57+
if let State::Alive = self.state.get() {
58+
self.value.get().cast()
59+
} else {
60+
unsafe { self.get_or_init_slow(i, f) }
5761
}
5862
}
5963

64+
/// # Safety
65+
/// The `self` reference must remain valid until the TLS destructor is run.
6066
#[cold]
61-
unsafe fn initialize(&self, i: Option<&mut Option<T>>, f: impl FnOnce() -> T) -> *const T {
62-
// Perform initialization
67+
unsafe fn get_or_init_slow(
68+
&self,
69+
i: Option<&mut Option<T>>,
70+
f: impl FnOnce() -> T,
71+
) -> *const T {
72+
match self.state.get() {
73+
State::Uninitialized => {}
74+
State::Alive => return self.value.get().cast(),
75+
State::Destroyed(_) => return ptr::null(),
76+
}
6377

6478
let v = i.and_then(Option::take).unwrap_or_else(f);
6579

66-
let old = unsafe { self.state.get().replace(State::Alive(v)) };
67-
match old {
80+
// SAFETY: we cannot be inside a `LocalKey::with` scope, as the initializer
81+
// has already returned and the next scope only starts after we return
82+
// the pointer. Therefore, there can be no references to the old value,
83+
// even if it was initialized. Thus because we are !Sync we have exclusive
84+
// access to self.value and may replace it.
85+
let mut old_value = unsafe { self.value.get().replace(MaybeUninit::new(v)) };
86+
match self.state.replace(State::Alive) {
6887
// If the variable is not being recursively initialized, register
6988
// the destructor. This might be a noop if the value does not need
7089
// destruction.
71-
State::Initial => D::register_dtor(self),
72-
// Else, drop the old value. This might be changed to a panic.
73-
val => drop(val),
74-
}
90+
State::Uninitialized => D::register_dtor(self),
7591

76-
// SAFETY: the state was just set to `Alive`
77-
unsafe {
78-
let State::Alive(v) = &*self.state.get() else { unreachable_unchecked() };
79-
v
92+
// Recursive initialization, we only need to drop the old value
93+
// as we've already registered the destructor.
94+
State::Alive => unsafe { old_value.assume_init_drop() },
95+
96+
State::Destroyed(_) => unreachable!(),
8097
}
98+
99+
self.value.get().cast()
81100
}
82101
}
83102

@@ -92,9 +111,12 @@ unsafe extern "C" fn destroy<T>(ptr: *mut u8) {
92111
// Print a nice abort message if a panic occurs.
93112
abort_on_dtor_unwind(|| {
94113
let storage = unsafe { &*(ptr as *const Storage<T, ()>) };
95-
// Update the state before running the destructor as it may attempt to
96-
// access the variable.
97-
let val = unsafe { storage.state.get().replace(State::Destroyed(())) };
98-
drop(val);
114+
if let State::Alive = storage.state.replace(State::Destroyed(())) {
115+
// SAFETY: we ensured the state was Alive so the value was initialized.
116+
// We also updated the state to Destroyed to prevent the destructor
117+
// from accessing the thread-local variable, as this would violate
118+
// the exclusive access provided by &mut T in Drop::drop.
119+
unsafe { (*storage.value.get()).assume_init_drop() }
120+
}
99121
})
100122
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//@ run-pass
2+
//@ needs-threads
3+
4+
use std::cell::Cell;
5+
use std::thread;
6+
7+
#[derive(Default)]
8+
struct Foo {
9+
ptr: Cell<*const Foo>,
10+
}
11+
12+
impl Foo {
13+
fn touch(&self) {
14+
if self.ptr.get().is_null() {
15+
self.ptr.set(self);
16+
} else {
17+
assert!(self.ptr.get() == self);
18+
}
19+
}
20+
}
21+
22+
impl Drop for Foo {
23+
fn drop(&mut self) {
24+
self.touch();
25+
}
26+
}
27+
28+
thread_local!(static FOO: Foo = Foo::default());
29+
30+
fn main() {
31+
thread::spawn(|| {
32+
FOO.with(|foo| foo.touch());
33+
FOO.with(|foo| foo.touch());
34+
})
35+
.join()
36+
.unwrap();
37+
}

0 commit comments

Comments
 (0)