From 4d336f5e5c978359760f5a73bb20a12d524a7e93 Mon Sep 17 00:00:00 2001 From: Brennan Kinney <5098581+polarathene@users.noreply.github.com> Date: Sat, 10 Jan 2026 15:45:42 +1300 Subject: [PATCH 1/2] refactor: Switch `ThreadIdManager` to use `OnceLock` --- Cargo.toml | 2 +- README.md | 2 +- src/thread_id.rs | 29 +++++++++++++++-------------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 13b498b..5ba04a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/Amanieu/thread_local-rs" readme = "README.md" keywords = ["thread_local", "concurrent", "thread"] edition = "2021" -rust-version = "1.63" +rust-version = "1.70" [features] # this feature provides performance improvements using nightly features diff --git a/README.md b/README.md index d055f9b..eaa52c8 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ thread_local = "1.1" ## Minimum Rust version -This crate's minimum supported Rust version (MSRV) is 1.63.0. +This crate's minimum supported Rust version (MSRV) is 1.70.0. ## License diff --git a/src/thread_id.rs b/src/thread_id.rs index 9111166..37eee6e 100644 --- a/src/thread_id.rs +++ b/src/thread_id.rs @@ -8,26 +8,26 @@ use std::cell::Cell; use std::cmp::Reverse; use std::collections::BinaryHeap; -use std::sync::Mutex; +use std::sync::{Mutex, OnceLock}; /// Thread ID manager which allocates thread IDs. It attempts to aggressively /// reuse thread IDs where possible to avoid cases where a ThreadLocal grows /// indefinitely when it is used by many short-lived threads. struct ThreadIdManager { free_from: usize, - free_list: Option>>, + free_list: BinaryHeap>, } impl ThreadIdManager { - const fn new() -> Self { + fn new() -> Self { Self { free_from: 0, - free_list: None, + free_list: BinaryHeap::new(), } } fn alloc(&mut self) -> usize { - if let Some(id) = self.free_list.as_mut().and_then(|heap| heap.pop()) { + if let Some(id) = self.free_list.pop() { id.0 } else { // `free_from` can't overflow as each thread takes up at least 2 bytes of memory and @@ -40,13 +40,14 @@ impl ThreadIdManager { } fn free(&mut self, id: usize) { - self.free_list - .get_or_insert_with(BinaryHeap::new) - .push(Reverse(id)); + self.free_list.push(Reverse(id)); } -} -static THREAD_ID_MANAGER: Mutex = Mutex::new(ThreadIdManager::new()); + fn singleton() -> &'static Mutex { + static THREAD_ID_MANAGER: OnceLock> = OnceLock::new(); + THREAD_ID_MANAGER.get_or_init(|| Mutex::new(ThreadIdManager::new())) + } +} /// Data which is unique to the current thread while it is running. /// A thread ID may be reused after a thread exits. @@ -103,7 +104,7 @@ cfg_if::cfg_if! { unsafe { THREAD = None; } - THREAD_ID_MANAGER.lock().unwrap().free(self.id.get()); + ThreadIdManager::singleton().lock().unwrap().free(self.id.get()); } } @@ -127,7 +128,7 @@ cfg_if::cfg_if! { /// Out-of-line slow path for allocating a thread ID. #[cold] fn get_slow() -> Thread { - let new = Thread::new(THREAD_ID_MANAGER.lock().unwrap().alloc()); + let new = Thread::new(ThreadIdManager::singleton().lock().unwrap().alloc()); unsafe { THREAD = Some(new); } @@ -156,7 +157,7 @@ cfg_if::cfg_if! { // will go through get_slow which will either panic or // initialize a new ThreadGuard. let _ = THREAD.try_with(|thread| thread.set(None)); - THREAD_ID_MANAGER.lock().unwrap().free(self.id.get()); + ThreadIdManager::singleton().lock().unwrap().free(self.id.get()); } } @@ -182,7 +183,7 @@ cfg_if::cfg_if! { /// Out-of-line slow path for allocating a thread ID. #[cold] fn get_slow(thread: &Cell>) -> Thread { - let new = Thread::new(THREAD_ID_MANAGER.lock().unwrap().alloc()); + let new = Thread::new(ThreadIdManager::singleton().lock().unwrap().alloc()); thread.set(Some(new)); THREAD_GUARD.with(|guard| guard.id.set(new.id())); new From 423e922ef791048b48584829c44c1d607a4ce6b4 Mon Sep 17 00:00:00 2001 From: Brennan Kinney <5098581+polarathene@users.noreply.github.com> Date: Sat, 10 Jan 2026 15:50:24 +1300 Subject: [PATCH 2/2] chore: Remove `OnceLock` from `ThreadIdManager` --- Cargo.toml | 2 +- README.md | 2 +- src/thread_id.rs | 20 +++++++++----------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ba04a0..83a7b46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/Amanieu/thread_local-rs" readme = "README.md" keywords = ["thread_local", "concurrent", "thread"] edition = "2021" -rust-version = "1.70" +rust-version = "1.80" [features] # this feature provides performance improvements using nightly features diff --git a/README.md b/README.md index eaa52c8..922a298 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ thread_local = "1.1" ## Minimum Rust version -This crate's minimum supported Rust version (MSRV) is 1.70.0. +This crate's minimum supported Rust version (MSRV) is 1.80.0. ## License diff --git a/src/thread_id.rs b/src/thread_id.rs index 37eee6e..35fdfd4 100644 --- a/src/thread_id.rs +++ b/src/thread_id.rs @@ -8,7 +8,7 @@ use std::cell::Cell; use std::cmp::Reverse; use std::collections::BinaryHeap; -use std::sync::{Mutex, OnceLock}; +use std::sync::Mutex; /// Thread ID manager which allocates thread IDs. It attempts to aggressively /// reuse thread IDs where possible to avoid cases where a ThreadLocal grows @@ -19,7 +19,7 @@ struct ThreadIdManager { } impl ThreadIdManager { - fn new() -> Self { + const fn new() -> Self { Self { free_from: 0, free_list: BinaryHeap::new(), @@ -42,13 +42,10 @@ impl ThreadIdManager { fn free(&mut self, id: usize) { self.free_list.push(Reverse(id)); } - - fn singleton() -> &'static Mutex { - static THREAD_ID_MANAGER: OnceLock> = OnceLock::new(); - THREAD_ID_MANAGER.get_or_init(|| Mutex::new(ThreadIdManager::new())) - } } +static THREAD_ID_MANAGER: Mutex = Mutex::new(ThreadIdManager::new()); + /// Data which is unique to the current thread while it is running. /// A thread ID may be reused after a thread exits. #[derive(Clone, Copy)] @@ -58,6 +55,7 @@ pub(crate) struct Thread { /// The index into the bucket this thread's local storage is in. pub(crate) index: usize, } + impl Thread { pub(crate) fn new(id: usize) -> Self { let bucket = (usize::BITS as usize) - ((id + 1).leading_zeros() as usize) - 1; @@ -104,7 +102,7 @@ cfg_if::cfg_if! { unsafe { THREAD = None; } - ThreadIdManager::singleton().lock().unwrap().free(self.id.get()); + THREAD_ID_MANAGER.lock().unwrap().free(self.id.get()); } } @@ -128,7 +126,7 @@ cfg_if::cfg_if! { /// Out-of-line slow path for allocating a thread ID. #[cold] fn get_slow() -> Thread { - let new = Thread::new(ThreadIdManager::singleton().lock().unwrap().alloc()); + let new = Thread::new(THREAD_ID_MANAGER.lock().unwrap().alloc()); unsafe { THREAD = Some(new); } @@ -157,7 +155,7 @@ cfg_if::cfg_if! { // will go through get_slow which will either panic or // initialize a new ThreadGuard. let _ = THREAD.try_with(|thread| thread.set(None)); - ThreadIdManager::singleton().lock().unwrap().free(self.id.get()); + THREAD_ID_MANAGER.lock().unwrap().free(self.id.get()); } } @@ -183,7 +181,7 @@ cfg_if::cfg_if! { /// Out-of-line slow path for allocating a thread ID. #[cold] fn get_slow(thread: &Cell>) -> Thread { - let new = Thread::new(ThreadIdManager::singleton().lock().unwrap().alloc()); + let new = Thread::new(THREAD_ID_MANAGER.lock().unwrap().alloc()); thread.set(Some(new)); THREAD_GUARD.with(|guard| guard.id.set(new.id())); new