diff --git a/library/std/src/thread/current.rs b/library/std/src/thread/current.rs index 508e35cefe88f..3512f04868303 100644 --- a/library/std/src/thread/current.rs +++ b/library/std/src/thread/current.rs @@ -246,7 +246,8 @@ pub(crate) fn current_or_unnamed() -> Thread { (*current).clone() } } else if current == DESTROYED { - Thread::new(id::get_or_init(), None) + let id = id::get_or_init(); + Thread::new_current(id) } else { init_current(current) } @@ -291,7 +292,7 @@ fn init_current(current: *mut ()) -> Thread { CURRENT.set(BUSY); // If the thread ID was initialized already, use it. let id = id::get_or_init(); - let thread = Thread::new(id, None); + let thread = Thread::new_current(id); // Make sure that `crate::rt::thread_cleanup` will be run, which will // call `drop_current`. diff --git a/library/std/src/thread/lifecycle.rs b/library/std/src/thread/lifecycle.rs index d3a97bbf08fa2..241f63d37f832 100644 --- a/library/std/src/thread/lifecycle.rs +++ b/library/std/src/thread/lifecycle.rs @@ -141,6 +141,10 @@ impl ThreadInit { rtabort!("current thread handle already set during thread spawn"); } + // The handle was created by the spawning thread, so only now that we are + // running can the OS id be filled in. + self.handle.set_os_id_to_current(); + if let Some(name) = self.handle.cname() { imp::set_name(name); } diff --git a/library/std/src/thread/tests.rs b/library/std/src/thread/tests.rs index 78b6f7c35e8db..d7f4a47cb4a47 100644 --- a/library/std/src/thread/tests.rs +++ b/library/std/src/thread/tests.rs @@ -356,6 +356,19 @@ fn test_thread_os_id_not_equal() { assert!(current_id != spawned_id); } +#[test] +fn test_thread_os_id_matches_current() { + assert_eq!(thread::current().os_id(), crate::sys::thread::current_os_id()); +} + +#[test] +fn test_thread_os_id_of_spawned_thread() { + let spawned = thread::spawn(|| thread::current().os_id()); + let handle = spawned.thread().clone(); + let spawned_id = spawned.join().unwrap(); + assert_eq!(handle.os_id(), spawned_id); +} + #[test] fn test_scoped_threads_drop_result_before_join() { let actually_finished = &AtomicBool::new(false); diff --git a/library/std/src/thread/thread.rs b/library/std/src/thread/thread.rs index 7c9c91c3b0c78..ff6affae7f7da 100644 --- a/library/std/src/thread/thread.rs +++ b/library/std/src/thread/thread.rs @@ -4,8 +4,9 @@ use crate::alloc::System; use crate::ffi::CStr; use crate::fmt; use crate::pin::Pin; -use crate::sync::Arc; +use crate::sync::{Arc, OnceLock}; use crate::sys::sync::Parker; +use crate::sys::thread as imp; use crate::time::Duration; // This module ensures private fields are kept private, which is necessary to enforce the safety requirements. @@ -49,6 +50,7 @@ use thread_name_string::ThreadNameString; struct Inner { name: Option, id: ThreadId, + os_id: OnceLock, parker: Parker, } @@ -103,6 +105,7 @@ impl Thread { let ptr = Arc::get_mut_unchecked(&mut arc).as_mut_ptr(); (&raw mut (*ptr).name).write(name); (&raw mut (*ptr).id).write(id); + (&raw mut (*ptr).os_id).write(OnceLock::new()); Parker::new_in_place(&raw mut (*ptr).parker); Pin::new_unchecked(arc.assume_init()) }; @@ -110,6 +113,34 @@ impl Thread { Thread { inner } } + /// Creates a handle for the calling thread, recording its OS id. + /// + /// `id` must be the `ThreadId` of the calling thread. + /// + /// Takes no name because passing one into `Thread::new` allocates with the + /// global allocator, which `thread::current` is documented never to use. + pub(crate) fn new_current(id: ThreadId) -> Thread { + let thread = Thread::new(id, None); + thread.set_os_id_to_current(); + thread + } + + /// Records the OS id of the calling thread in this handle. + /// + /// May only be called from the thread to which this handle belongs. A + /// spawned thread does this itself once it starts running, since its handle + /// already exists by then. + /// + /// `imp::current_os_id` must not allocate with the global allocator or call + /// `thread::current`. + pub(crate) fn set_os_id_to_current(&self) { + if let Some(os_id) = imp::current_os_id() { + if self.inner.os_id.set(os_id).is_err() { + rtabort!("thread OS id already set"); + } + } + } + /// Like the public [`park`], but callable on any handle. This is used to /// allow parking in TLS destructors. /// @@ -204,6 +235,35 @@ impl Thread { self.inner.id } + /// Gets the id the operating system gave this thread, if it has one that can + /// be read. + /// + /// This is the id that shows up in tools like `ps` and `top`, debuggers and + /// crash logs, unlike [`ThreadId`], which has no guaranteed relationship to + /// it. `None` means the platform has no such id, the thread has not started + /// running yet, or the id could not be read. + /// + /// The operating system may reuse the id of a thread that has exited, and a + /// `Thread` handle can outlive the thread it refers to. Use the id only + /// where a reused id is harmless, such as logging. + /// + /// # Examples + /// + /// ``` + /// #![feature(thread_os_id)] + /// use std::thread; + /// + /// let spawned = thread::spawn(|| thread::current().os_id()).join().unwrap(); + /// if spawned.is_some() { + /// assert_ne!(spawned, thread::current().os_id()); + /// } + /// ``` + #[unstable(feature = "thread_os_id", issue = "160215")] + #[must_use] + pub fn os_id(&self) -> Option { + self.inner.os_id.get().copied() + } + /// Gets the thread's name. /// /// For more information about named threads, see