thread_local/thread_id.rs
1// Copyright 2017 Amanieu d'Antras
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use std::cell::Cell;
9use std::cmp::Reverse;
10use std::collections::BinaryHeap;
11use std::sync::Mutex;
12
13/// Thread ID manager which allocates thread IDs. It attempts to aggressively
14/// reuse thread IDs where possible to avoid cases where a ThreadLocal grows
15/// indefinitely when it is used by many short-lived threads.
16struct ThreadIdManager {
17 free_from: usize,
18 free_list: Option<BinaryHeap<Reverse<usize>>>,
19}
20
21impl ThreadIdManager {
22 const fn new() -> Self {
23 Self {
24 free_from: 0,
25 free_list: None,
26 }
27 }
28
29 fn alloc(&mut self) -> usize {
30 if let Some(id) = self.free_list.as_mut().and_then(|heap| heap.pop()) {
31 id.0
32 } else {
33 // `free_from` can't overflow as each thread takes up at least 2 bytes of memory and
34 // thus we can't even have `usize::MAX / 2 + 1` threads.
35
36 let id = self.free_from;
37 self.free_from += 1;
38 id
39 }
40 }
41
42 fn free(&mut self, id: usize) {
43 self.free_list
44 .get_or_insert_with(BinaryHeap::new)
45 .push(Reverse(id));
46 }
47}
48
49static THREAD_ID_MANAGER: Mutex<ThreadIdManager> = Mutex::new(ThreadIdManager::new());
50
51/// Data which is unique to the current thread while it is running.
52/// A thread ID may be reused after a thread exits.
53#[derive(Clone, Copy)]
54pub(crate) struct Thread {
55 /// The bucket this thread's local storage will be in.
56 pub(crate) bucket: usize,
57 /// The index into the bucket this thread's local storage is in.
58 pub(crate) index: usize,
59}
60impl Thread {
61 pub(crate) fn new(id: usize) -> Self {
62 let bucket = (usize::BITS as usize) - ((id + 1).leading_zeros() as usize) - 1;
63 let bucket_size = 1 << bucket;
64 let index = id - (bucket_size - 1);
65
66 Self { bucket, index }
67 }
68
69 /// The size of the bucket this thread's local storage will be in.
70 pub(crate) fn bucket_size(&self) -> usize {
71 1 << self.bucket
72 }
73
74 /// The thread ID obtained from the thread ID manager.
75 pub(crate) fn id(&self) -> usize {
76 self.index + (self.bucket_size() - 1)
77 }
78}
79
80cfg_if::cfg_if! {
81 if #[cfg(feature = "nightly")] {
82 // This is split into 2 thread-local variables so that we can check whether the
83 // thread is initialized without having to register a thread-local destructor.
84 //
85 // This makes the fast path smaller.
86 #[thread_local]
87 static mut THREAD: Option<Thread> = None;
88 thread_local! { static THREAD_GUARD: ThreadGuard = const { ThreadGuard { id: Cell::new(0) } }; }
89
90 // Guard to ensure the thread ID is released on thread exit.
91 struct ThreadGuard {
92 // We keep a copy of the thread ID in the ThreadGuard: we can't
93 // reliably access THREAD in our Drop impl due to the unpredictable
94 // order of TLS destructors.
95 id: Cell<usize>,
96 }
97
98 impl Drop for ThreadGuard {
99 fn drop(&mut self) {
100 // Release the thread ID. Any further accesses to the thread ID
101 // will go through get_slow which will either panic or
102 // initialize a new ThreadGuard.
103 unsafe {
104 THREAD = None;
105 }
106 THREAD_ID_MANAGER.lock().unwrap().free(self.id.get());
107 }
108 }
109
110 /// Returns a thread ID for the current thread, **not** allocating one if needed.
111 /// This avoids registering a thread-local destructor.
112 #[inline]
113 pub(crate) fn try_get() -> Option<Thread> {
114 unsafe { THREAD }
115 }
116
117 /// Returns a thread ID for the current thread, allocating one if needed.
118 #[inline]
119 pub(crate) fn get() -> Thread {
120 if let Some(thread) = unsafe { THREAD } {
121 thread
122 } else {
123 get_slow()
124 }
125 }
126
127 /// Out-of-line slow path for allocating a thread ID.
128 #[cold]
129 fn get_slow() -> Thread {
130 let new = Thread::new(THREAD_ID_MANAGER.lock().unwrap().alloc());
131 unsafe {
132 THREAD = Some(new);
133 }
134 THREAD_GUARD.with(|guard| guard.id.set(new.id()));
135 new
136 }
137 } else {
138 // This is split into 2 thread-local variables so that we can check whether the
139 // thread is initialized without having to register a thread-local destructor.
140 //
141 // This makes the fast path smaller.
142 thread_local! { static THREAD: Cell<Option<Thread>> = const { Cell::new(None) }; }
143 thread_local! { static THREAD_GUARD: ThreadGuard = const { ThreadGuard { id: Cell::new(0) } }; }
144
145 // Guard to ensure the thread ID is released on thread exit.
146 struct ThreadGuard {
147 // We keep a copy of the thread ID in the ThreadGuard: we can't
148 // reliably access THREAD in our Drop impl due to the unpredictable
149 // order of TLS destructors.
150 id: Cell<usize>,
151 }
152
153 impl Drop for ThreadGuard {
154 fn drop(&mut self) {
155 // Release the thread ID. Any further accesses to the thread ID
156 // will go through get_slow which will either panic or
157 // initialize a new ThreadGuard.
158 let _ = THREAD.try_with(|thread| thread.set(None));
159 THREAD_ID_MANAGER.lock().unwrap().free(self.id.get());
160 }
161 }
162
163 /// Returns a thread ID for the current thread, **not** allocating one if needed.
164 /// This avoids registering a thread-local destructor.
165 #[inline]
166 pub(crate) fn try_get() -> Option<Thread> {
167 THREAD.with(|thread| thread.get())
168 }
169
170 /// Returns a thread ID for the current thread, allocating one if needed.
171 #[inline]
172 pub(crate) fn get() -> Thread {
173 THREAD.with(|thread| {
174 if let Some(thread) = thread.get() {
175 thread
176 } else {
177 get_slow(thread)
178 }
179 })
180 }
181
182 /// Out-of-line slow path for allocating a thread ID.
183 #[cold]
184 fn get_slow(thread: &Cell<Option<Thread>>) -> Thread {
185 let new = Thread::new(THREAD_ID_MANAGER.lock().unwrap().alloc());
186 thread.set(Some(new));
187 THREAD_GUARD.with(|guard| guard.id.set(new.id()));
188 new
189 }
190 }
191}
192
193#[test]
194fn test_thread() {
195 let thread = Thread::new(0);
196 assert_eq!(thread.id(), 0);
197 assert_eq!(thread.bucket, 0);
198 assert_eq!(thread.bucket_size(), 1);
199 assert_eq!(thread.index, 0);
200
201 let thread = Thread::new(1);
202 assert_eq!(thread.id(), 1);
203 assert_eq!(thread.bucket, 1);
204 assert_eq!(thread.bucket_size(), 2);
205 assert_eq!(thread.index, 0);
206
207 let thread = Thread::new(2);
208 assert_eq!(thread.id(), 2);
209 assert_eq!(thread.bucket, 1);
210 assert_eq!(thread.bucket_size(), 2);
211 assert_eq!(thread.index, 1);
212
213 let thread = Thread::new(3);
214 assert_eq!(thread.id(), 3);
215 assert_eq!(thread.bucket, 2);
216 assert_eq!(thread.bucket_size(), 4);
217 assert_eq!(thread.index, 0);
218
219 let thread = Thread::new(19);
220 assert_eq!(thread.id(), 19);
221 assert_eq!(thread.bucket, 4);
222 assert_eq!(thread.bucket_size(), 16);
223 assert_eq!(thread.index, 4);
224}