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