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.
78use crate::POINTER_WIDTH;
9use once_cell::sync::Lazy;
10use std::cell::Cell;
11use std::cmp::Reverse;
12use std::collections::BinaryHeap;
13use std::sync::Mutex;
1415/// 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 {
23fn new() -> Self {
24Self {
25 free_from: 0,
26 free_list: BinaryHeap::new(),
27 }
28 }
29fn alloc(&mut self) -> usize {
30if 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.
3536let id = self.free_from;
37self.free_from += 1;
38 id
39 }
40 }
41fn free(&mut self, id: usize) {
42self.free_list.push(Reverse(id));
43 }
44}
45static THREAD_ID_MANAGER: Lazy<Mutex<ThreadIdManager>> =
46 Lazy::new(|| Mutex::new(ThreadIdManager::new()));
4748/// 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.
53pub(crate) id: usize,
54/// The bucket this thread's local storage will be in.
55pub(crate) bucket: usize,
56/// The size of the bucket this thread's local storage will be in.
57pub(crate) bucket_size: usize,
58/// The index into the bucket this thread's local storage is in.
59pub(crate) index: usize,
60}
61impl Thread {
62pub(crate) fn new(id: usize) -> Self {
63let bucket = usize::from(POINTER_WIDTH) - ((id + 1).leading_zeros() as usize) - 1;
64let bucket_size = 1 << bucket;
65let index = id - (bucket_size - 1);
6667Self {
68 id,
69 bucket,
70 bucket_size,
71 index,
72 }
73 }
74}
7576cfg_if::cfg_if! {
77if #[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]
83static mut THREAD: Option<Thread> = None;
84thread_local! { static THREAD_GUARD: ThreadGuard = const { ThreadGuard { id: Cell::new(0) } }; }
8586// Guard to ensure the thread ID is released on thread exit.
87struct 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.
91id: Cell<usize>,
92 }
9394impl Drop for ThreadGuard {
95fn 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.
99unsafe {
100 THREAD = None;
101 }
102 THREAD_ID_MANAGER.lock().unwrap().free(self.id.get());
103 }
104 }
105106/// Returns a thread ID for the current thread, allocating one if needed.
107#[inline]
108pub(crate) fn get() -> Thread {
109if let Some(thread) = unsafe { THREAD } {
110 thread
111 } else {
112 get_slow()
113 }
114 }
115116/// Out-of-line slow path for allocating a thread ID.
117#[cold]
118fn get_slow() -> Thread {
119let new = Thread::new(THREAD_ID_MANAGER.lock().unwrap().alloc());
120unsafe {
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.
131thread_local! { static THREAD: Cell<Option<Thread>> = const { Cell::new(None) }; }
132thread_local! { static THREAD_GUARD: ThreadGuard = const { ThreadGuard { id: Cell::new(0) } }; }
133134// Guard to ensure the thread ID is released on thread exit.
135struct 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.
139id: Cell<usize>,
140 }
141142impl Drop for ThreadGuard {
143fn 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.
147let _ = THREAD.try_with(|thread| thread.set(None));
148 THREAD_ID_MANAGER.lock().unwrap().free(self.id.get());
149 }
150 }
151152/// Returns a thread ID for the current thread, allocating one if needed.
153#[inline]
154pub(crate) fn get() -> Thread {
155 THREAD.with(|thread| {
156if let Some(thread) = thread.get() {
157 thread
158 } else {
159 get_slow(thread)
160 }
161 })
162 }
163164/// Out-of-line slow path for allocating a thread ID.
165#[cold]
166fn get_slow(thread: &Cell<Option<Thread>>) -> Thread {
167let 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}
174175#[test]
176fn test_thread() {
177let thread = Thread::new(0);
178assert_eq!(thread.id, 0);
179assert_eq!(thread.bucket, 0);
180assert_eq!(thread.bucket_size, 1);
181assert_eq!(thread.index, 0);
182183let thread = Thread::new(1);
184assert_eq!(thread.id, 1);
185assert_eq!(thread.bucket, 1);
186assert_eq!(thread.bucket_size, 2);
187assert_eq!(thread.index, 0);
188189let thread = Thread::new(2);
190assert_eq!(thread.id, 2);
191assert_eq!(thread.bucket, 1);
192assert_eq!(thread.bucket_size, 2);
193assert_eq!(thread.index, 1);
194195let thread = Thread::new(3);
196assert_eq!(thread.id, 3);
197assert_eq!(thread.bucket, 2);
198assert_eq!(thread.bucket_size, 4);
199assert_eq!(thread.index, 0);
200201let thread = Thread::new(19);
202assert_eq!(thread.id, 19);
203assert_eq!(thread.bucket, 4);
204assert_eq!(thread.bucket_size, 16);
205assert_eq!(thread.index, 4);
206}