std\sys\thread_local\key/windows.rs
1//! Implementation of `LazyKey` for Windows.
2//!
3//! Windows has no native support for running destructors so we manage our own
4//! list of destructors to keep track of how to destroy keys. We then install a
5//! callback later to get invoked whenever a thread exits, running all
6//! appropriate destructors (see the [`guard`](guard) module documentation).
7//!
8//! This will likely need to be improved over time, but this module attempts a
9//! "poor man's" destructor callback system. Once we've got a list of what to
10//! run, we iterate over all keys, check their values, and then run destructors
11//! if the values turn out to be non null (setting them to null just beforehand).
12//! We do this a few times in a loop to basically match Unix semantics. If we
13//! don't reach a fixed point after a short while then we just inevitably leak
14//! something.
15//!
16//! The list is implemented as an atomic single-linked list of `LazyKey`s and
17//! does not support unregistration. Unfortunately, this means that we cannot
18//! use racy initialization for creating the keys in `LazyKey`, as that could
19//! result in destructors being missed. Hence, we synchronize the creation of
20//! keys with destructors through [`INIT_ONCE`](c::INIT_ONCE) (`std`'s
21//! [`Once`](crate::sync::Once) cannot be used since it might use TLS itself).
22//! For keys without destructors, racy initialization suffices.
23
24// FIXME: investigate using a fixed-size array instead, as the maximum number
25// of keys is [limited to 1088](https://learn.microsoft.com/en-us/windows/win32/ProcThread/thread-local-storage).
26
27use crate::cell::UnsafeCell;
28use crate::ptr;
29use crate::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, Release};
30use crate::sync::atomic::{Atomic, AtomicPtr, AtomicU32};
31use crate::sys::c;
32use crate::sys::thread_local::guard;
33
34pub type Key = u32;
35type Dtor = unsafe extern "C" fn(*mut u8);
36
37pub struct LazyKey {
38 /// The key value shifted up by one. Since TLS_OUT_OF_INDEXES == u32::MAX
39 /// is not a valid key value, this allows us to use zero as sentinel value
40 /// without risking overflow.
41 key: Atomic<Key>,
42 dtor: Option<Dtor>,
43 next: Atomic<*mut LazyKey>,
44 /// Currently, destructors cannot be unregistered, so we cannot use racy
45 /// initialization for keys. Instead, we need synchronize initialization.
46 /// Use the Windows-provided `Once` since it does not require TLS.
47 once: UnsafeCell<c::INIT_ONCE>,
48}
49
50impl LazyKey {
51 #[inline]
52 pub const fn new(dtor: Option<Dtor>) -> LazyKey {
53 LazyKey {
54 key: AtomicU32::new(0),
55 dtor,
56 next: AtomicPtr::new(ptr::null_mut()),
57 once: UnsafeCell::new(c::INIT_ONCE_STATIC_INIT),
58 }
59 }
60
61 #[inline]
62 pub fn force(&'static self) -> Key {
63 if self.dtor.is_some() {
64 // Needs to be called on all threads where the key might have a non-null value!
65 // Otherwise, `run_dtors` might not be called on this thread.
66 guard::enable();
67 }
68
69 match self.key.load(Acquire) {
70 0 => unsafe { self.init() },
71 key => key - 1,
72 }
73 }
74
75 #[cold]
76 unsafe fn init(&'static self) -> Key {
77 if self.dtor.is_some() {
78 let mut pending = c::FALSE;
79 let r = unsafe {
80 c::InitOnceBeginInitialize(self.once.get(), 0, &mut pending, ptr::null_mut())
81 };
82 assert_eq!(r, c::TRUE);
83
84 if pending == c::FALSE {
85 // Some other thread initialized the key, load it.
86 self.key.load(Relaxed) - 1
87 } else {
88 let key = unsafe { c::TlsAlloc() };
89 if key == c::TLS_OUT_OF_INDEXES {
90 // Since we abort the process, there is no need to wake up
91 // the waiting threads. If this were a panic, the wakeup
92 // would need to occur first in order to avoid deadlock.
93 rtabort!("out of TLS indexes");
94 }
95
96 unsafe {
97 // Add ourselves to the `DTORS` list, so that when `run_dtors` gets called,
98 // our dtor is invoked.
99 register_dtor(self);
100 }
101
102 // Release-storing the key needs to be the last thing we do.
103 // This is because in `fn key()`, other threads will do an acquire load of the key,
104 // and if that sees this write then it will entirely bypass the `InitOnce`. We thus
105 // need to establish synchronization through `key`. In particular that acquire load
106 // must happen-after the register_dtor above, to ensure the dtor actually runs!
107 self.key.store(key + 1, Release);
108
109 let r = unsafe { c::InitOnceComplete(self.once.get(), 0, ptr::null_mut()) };
110 debug_assert_eq!(r, c::TRUE);
111
112 key
113 }
114 } else {
115 // If there is no destructor to clean up, we can use racy initialization.
116
117 let key = unsafe { c::TlsAlloc() };
118 if key == c::TLS_OUT_OF_INDEXES {
119 rtabort!("out of TLS indexes");
120 }
121
122 match self.key.compare_exchange(0, key + 1, AcqRel, Acquire) {
123 Ok(_) => key,
124 Err(new) => unsafe {
125 // Some other thread completed initialization first, so destroy
126 // our key and use theirs.
127 let r = c::TlsFree(key);
128 debug_assert_eq!(r, c::TRUE);
129 new - 1
130 },
131 }
132 }
133 }
134}
135
136unsafe impl Send for LazyKey {}
137unsafe impl Sync for LazyKey {}
138
139#[inline]
140pub unsafe fn set(key: Key, val: *mut u8) {
141 let r = unsafe { c::TlsSetValue(key, val.cast()) };
142 debug_assert_eq!(r, c::TRUE);
143}
144
145#[inline]
146pub unsafe fn get(key: Key) -> *mut u8 {
147 unsafe { c::TlsGetValue(key).cast() }
148}
149
150static DTORS: Atomic<*mut LazyKey> = AtomicPtr::new(ptr::null_mut());
151
152/// Should only be called once per key, otherwise loops or breaks may occur in
153/// the linked list.
154unsafe fn register_dtor(key: &'static LazyKey) {
155 let this = <*const LazyKey>::cast_mut(key);
156 // Use acquire ordering to pass along the changes done by the previously
157 // registered keys when we store the new head with release ordering.
158 let mut head = DTORS.load(Acquire);
159 loop {
160 key.next.store(head, Relaxed);
161 match DTORS.compare_exchange_weak(head, this, Release, Acquire) {
162 Ok(_) => break,
163 Err(new) => head = new,
164 }
165 }
166}
167
168/// This will and must only be run by the destructor callback in [`guard`].
169pub unsafe fn run_dtors() {
170 for _ in 0..5 {
171 let mut any_run = false;
172
173 // Use acquire ordering to observe key initialization.
174 let mut cur = DTORS.load(Acquire);
175 while !cur.is_null() {
176 let pre_key = unsafe { (*cur).key.load(Acquire) };
177 let dtor = unsafe { (*cur).dtor.unwrap() };
178 cur = unsafe { (*cur).next.load(Relaxed) };
179
180 // In LazyKey::init, we register the dtor before setting `key`.
181 // So if one thread's `run_dtors` races with another thread executing `init` on the same
182 // `LazyKey`, we can encounter a key of 0 here. That means this key was never
183 // initialized in this thread so we can safely skip it.
184 if pre_key == 0 {
185 continue;
186 }
187 // If this is non-zero, then via the `Acquire` load above we synchronized with
188 // everything relevant for this key. (It's not clear that this is needed, since the
189 // release-acquire pair on DTORS also establishes synchronization, but better safe than
190 // sorry.)
191 let key = pre_key - 1;
192
193 let ptr = unsafe { c::TlsGetValue(key) };
194 if !ptr.is_null() {
195 unsafe {
196 c::TlsSetValue(key, ptr::null_mut());
197 dtor(ptr as *mut _);
198 any_run = true;
199 }
200 }
201 }
202
203 if !any_run {
204 break;
205 }
206 }
207}