Skip to main content

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}