Implement Thread::os_id - #160219
Conversation
| use thread_name_string::ThreadNameString; | ||
|
|
||
| // The handle of a spawned thread exists before the thread does, so the thread | ||
| // stores its own id once it starts running, hence the atomic. 0 means "not known". |
There was a problem hiding this comment.
Nothing guarantees that the OS's ID is non-zero, we really shouldn't use zero as a sentinel.
There was a problem hiding this comment.
Switched to OnceLock: write-once without a sentinel, and it needs no 64-bit atomic so the cfg_select is gone too.
Waiting would need the child to always store the value, so OnceLock<Option>.
I'm also not sure how it works out for spawn hooks, which get &Thread on the parent before the thread exists.
You mentioned you have an implementation, so if you've already worked that out I'd rather build on it than guess.
|
Reminder, once the PR becomes ready for a review, use |
|
@rustbot ready |
|
A meta comment: I appreciate your recent contributions to the project, you are clearly interested in helping out and invest time and thought into it. At the same time, especially the description of this PR makes it very obvious that you are using a LLM to aid you in your contributions, both in writing code and comments. These kinds of cases have been very intensely discussed within the project, and the result of that discussion is our LLM policy that will come into effect next week. Under that policy your LLM usage is deemed forbidden, and failing to declare it (like you have been doing) may result in moderation actions against you. I'm not a moderator, and this is not a moderation warning, just some friendly advice that I want to give you, stemming from years of working on the standard library: In my experience, the hard part about working on the standard library is not actually writing the code, but in
An LLM can do neither those things. While they are admittedly good at doing the mechanical task of writing the code, they are awfully unreliable when it comes to providing evidence, have little to no intrinsic capability to exercise good judgement and are fundamentally just not you. As an example, the current description of this PR is mostly just a very detailed summary of its changes. I can see those myself, thank you very much, that's what the "Files changed" tab is for! The much more interesting questions in this case are e.g. why Thus, please, remember to take time to think and research and be the author, not just the editor, of your communication and your code. If you want to learn how best to use (or not use) LLMs, feel free to join our LLM-mentoring channel on Zulip. As for me, I'm not interested in some stochastic parrot's output, but in other people's, since that's what I learn and thrive from. That's why I invest my time in this project anyway, and am very happy to help other people out if they get stuck. I must insist however that the responsibility of doing the thinking and research doesn't fall on my shoulders alone. @rustbot author |
Thanks for your advice, I totally agree with inappropriate use of LLM in this PR, I usually trying to take more time preparing the PR and understanding all the nuances, this one was bad and too heavily relied on AI. Thanks and I will take it into consideration. As for this PR, I will take some time to really reason about it and also consider other approaches. Sorry for that. :) |
| /// use std::thread; | ||
| /// | ||
| /// let spawned = thread::spawn(|| thread::current().os_id()); | ||
| /// println!("spawned thread ran as {:?}", spawned.join().unwrap()); |
There was a problem hiding this comment.
Could this do the assert_ne! test here? I think that demos relevant properties a bit better.
There was a problem hiding this comment.
Thanks, applied the changes but guarded it so it will not fail on other platforms that do not support os_id and could return None making it fail.
| /// The operating system may hand the same id to a later thread once this one | ||
| /// exits, so it does not name a thread uniquely over the life of the | ||
| /// process. It may also no longer refer to this thread at all, since any | ||
| /// thread but the current one can exit at any point. For anything other than | ||
| /// the current thread, logging is the only safe use. |
There was a problem hiding this comment.
It's usable for any thread that is running, not just the current right?
I think the property to convey is that OS TIDs uniquely represent a thread among other running threads, which effectively means that if a thread isn't known to be running then ID can only be used in cases where non-uniqueness is okay (e.g. logging). And then one way to know the thread is running is if you're looking at the current thread's ID.
Not sure how best to put this into words.
There was a problem hiding this comment.
So there are conditions under which it is safe to use os_id and it refers to correct thread, however in other conditions it should be used only for cases when stale os_id reference is harmless, like logging.
I have tried to reword this section to better communicate this, thank. Let me know if you see any better way to put this into words.
| /// | ||
| /// 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 or offers no way to read it, |
There was a problem hiding this comment.
Some sandboxes or containers allow per-function filtering, so it is also possible the thread ID exists with an API to read it, but the process can't do that.
| /// it. `None` means the platform has no such id or offers no way to read it, | |
| /// it. `None` means the platform has no such id or it can't be read, |
There was a problem hiding this comment.
Thanks, applied your suggestion to reword it slightly.
| let thread = Thread::new(id::get_or_init(), None); | ||
| thread.set_os_id_to_current(); |
There was a problem hiding this comment.
Why doesn't Thread::new just call set_os_id_to_current internally?
If there are callers where this doesn't work, maybe we should have two constructors?
Thread::new_currentuses current OS idThread::new_remotetakes OS id as paramter
I think this would also avoid the OnceLock.
There was a problem hiding this comment.
I added Thread::new_current just for the current path, however I think we couldn't get rid of sync primitives for os_id because of the spawn_unchecked path, in that case when we create the Thread, we are still running on parent thread, so using set_os_id_to_current would assign parent os_id to child thread that it is trying to spawn.
- Thread::new lifecycle.rs:48 (imp::current_os_id() returns the parent's TID)
- imp::Thread::new lifecycle.rs:116
|
@rustbot ready I have addressed suggested refinements. PR body was updated to better explain decisions for the implementation and further possible refinements, it is pretty long and detailed but I think those nuances worth explaining properly. |
|
Sorry I'm at capacity for the next two weeks. @rustbot reroll |
|
@rustbot ready |
There was a problem hiding this comment.
left a couple nits. one is probably fine with just a doc change, the other i'm unsure of - if there's no way to make this resilient across forks without just querying the thread id every time then that's Sad but may be justifiable perf-wise. either way, impl looks mostly good.
@rustbot author
| /// spawned thread does this itself once it starts running, since its handle | ||
| /// already exists by then. | ||
| pub(crate) fn set_os_id_to_current(&self) { | ||
| if let Some(os_id) = imp::current_os_id() { |
There was a problem hiding this comment.
So, on SGX we just get the thread address? I guess that's justifiable but it means that the meaning of os_id is platform-specific; this thread ID won't show up in a debugger or the likes.
Then again I don't know if anyone cares to use debuggers in SGX, so /shrug I wouldn't consider this blocking but it could use a doc note
| #[unstable(feature = "thread_os_id", issue = "160215")] | ||
| #[must_use] | ||
| pub fn os_id(&self) -> Option<u64> { | ||
| self.inner.os_id.get().copied() |
There was a problem hiding this comment.
this will be stale after a fork, and i think it is a reasonable want of an api consumer to wish to be able to use this in conjunction with forking. unsure if there's a nice way to check whether we have forked, though
There was a problem hiding this comment.
Yes, after fork the stored os_id will be stale. It may be fixed by querying the current_os_id when it's current thread (no way for querying non current thread because we are not storing pthread_t), so something like these could be added:
if self.id() == current_id() { imp::current_os_id() } else { cached }
Or detecting a fork, but we will have to store extra fields, and making additional syscall, we could store stored_pid alongside os_id at start.:
if getpid() != stored_pid
To be honest nothing else comes to my mind, and not sure if it's worth it?
diff --git a/library/std/src/thread/thread.rs b/library/std/src/thread/thread.rs index ff6affa..d70c244 100644 --- a/library/std/src/thread/thread.rs +++ b/library/std/src/thread/thread.rs @@ -125,7 +125,8 @@ pub(crate) fn new_current(id: ThreadId) -> Thread { thread } - /// Records the OS id of the calling thread in this handle. + /// Records the calling thread's OS id, as reported by + /// `imp::current_os_id`, 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 @@ -240,12 +241,17 @@ pub fn id(&self) -> ThreadId { /// /// 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. + /// it. On a platform with no OS-visible thread id, such as SGX, the value + /// may be some other per-thread value (there, the thread's address), which + /// such tools will not recognize. `None` means no id could be recorded: the + /// thread has not started running yet, or the platform has no way to read + /// one. /// /// 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. + /// `Thread` handle can outlive the thread it refers to. After a `fork`, the + /// id recorded in the child process still refers to the parent's thread; it + /// is not re-read. Use the id only where a reused or stale id is harmless, + /// such as logging. /// /// # Examples ///
|
|
@rustbot ready |
|
looks good! head commit is a bit stale so let's test it. i'll r+ if this passes. @bors try |
This comment has been minimized.
This comment has been minimized.
Implement `Thread::os_id`
|
@bors r+ |
Implement `Thread::os_id` Implements `Thread::os_id` as an unstable feature, per the accepted ACP rust-lang/libs-team#635. Tracking issue: rust-lang#160215 `os_id` returns the OS-level thread id, so Rust programs can tie their own logs to system-level logs (the ACP's stated motivation). - Using the existing `current_os_id` is much simpler than pulling the id off `imp::Thread` in `spawn_unchecked`. That needs a per-platform arm, and the child still has to fill it in on platforms without a by handle query, so it'd be extra on top of this rather than instead of it. - `Thread` is handed to user code by spawn hooks before the native thread exists, so the id can only be filled in later. There's no spare `u64` value to mean "not set yet", so a OnceLock is chosen as a simple primitive to use for this purpose. r? libs
…uwer Rollup of 9 pull requests Successful merges: - #157738 (Support move expressions in coroutine closures) - #160219 (Implement `Thread::os_id`) - #162449 (Prefer removing a redundant shared reference over reborrow) - #162494 (Ignore `self-in-const-generics` test for parallel frontend) - #162495 (Reserve items in `Extend` implementations) - #162238 (trait solver: Include implied outlives assumptions) - #162473 (Small `x perf` improvements) - #162489 (Clean up on upvar_tys) - #162500 (Move the `expect-item-after-attribute.rs` test to the correct directory)
Rollup merge of #160219 - valentynkit:thread-os-id, r=nia-e Implement `Thread::os_id` Implements `Thread::os_id` as an unstable feature, per the accepted ACP rust-lang/libs-team#635. Tracking issue: #160215 `os_id` returns the OS-level thread id, so Rust programs can tie their own logs to system-level logs (the ACP's stated motivation). - Using the existing `current_os_id` is much simpler than pulling the id off `imp::Thread` in `spawn_unchecked`. That needs a per-platform arm, and the child still has to fill it in on platforms without a by handle query, so it'd be extra on top of this rather than instead of it. - `Thread` is handed to user code by spawn hooks before the native thread exists, so the id can only be filled in later. There's no spare `u64` value to mean "not set yet", so a OnceLock is chosen as a simple primitive to use for this purpose. r? libs
View all comments
Implements
Thread::os_idas an unstable feature, per the accepted ACP rust-lang/libs-team#635.Tracking issue: #160215
os_idreturns the OS-level thread id, so Rust programs can tie their own logs to system-level logs (the ACP's stated motivation).current_os_idis much simpler than pulling the id offimp::Threadinspawn_unchecked. That needs a per-platform arm, and the child still has to fill it in on platforms without a by handle query, so it'd be extra on top of this rather than instead of it.Threadis handed to user code by spawn hooks before the native thread exists, so the id can only be filled in later. There's no spareu64value to mean "not set yet", so a OnceLock is chosen as a simple primitive to use for this purpose.r? libs