Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

std: lazily allocate the main thread handle #132654

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

joboet
Copy link
Member

@joboet joboet commented Nov 5, 2024

#123550 eliminated the allocation of the main thread handle, but at the cost of greatly increased complexity. This PR proposes another approach: Instead of creating the main thread handle itself, the runtime simply remembers the thread ID of the main thread. The main thread handle is then only allocated when it is used, using the same lazy-initialization mechanism as for non-runtime use of thread::current, and the name method uses the thread ID to identify the main thread handle and return the correct name ("main") for it.

Thereby, we also allow accessing thread::current before main: as the runtime no longer tries to install its own handle, this will no longer trigger an abort. Rather, the name returned from name will only be "main" after the runtime initialization code has run, but I think that is acceptable.

This new approach also requires some changes to the signal handling code, as calling thread::current would now allocate when called on the main thread, which is not acceptable. I fixed this by adding a new function (with_current_name) that performs all the naming logic without allocation or without initializing the thread ID (which could allocate on some platforms).

Reverts #123550, CC @GnomedDev

@rustbot
Copy link
Collaborator

rustbot commented Nov 5, 2024

r? @ChrisDenton

rustbot has assigned @ChrisDenton.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. labels Nov 5, 2024
@joboet joboet changed the title Lazy main std: lazily allocate the main thread handle Nov 5, 2024
@rust-log-analyzer

This comment has been minimized.

@workingjubilee
Copy link
Member

workingjubilee commented Nov 5, 2024

I've also moved the Thread code into its own module, the size of thread/mod.rs is just getting out of hand.

std::thread::thread here we come?

EDIT: I was right!

Copy link
Member

@ChrisDenton ChrisDenton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be happy with this approach but it would be nice to have some consensus on it to (hopefully) avoid further churn. I won't insist on it though.

Comment on lines 1214 to 1210
#[cfg(not(target_thread_local))]
#[allow(unused)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this change is needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this is also used when 64-bit atomics are unavailable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any more specific cfg that can be used? #[allow(unused)], while sometimes necessary, makes me nervous because it could silently become actual dead code in the future.

Comment on lines 13 to 14
// INVARIANT: must be valid UTF-8
name: Option<CString>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change seems worse. Having an invariant that can be violated in safe code is not great.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also wondered if it made sense to have a new-type that held both invariants (UTF-8 & c-string \0 ending) ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Utf8CStr is a fairly common type in the ecosystem, it would make sense for us to start tinkering with providing it.

@ChrisDenton
Copy link
Member

Also could you separate the file move into a different commit? It's easier to diff that way.

@rust-log-analyzer

This comment has been minimized.

@joboet
Copy link
Member Author

joboet commented Nov 7, 2024

Blocked on the efficiency fix in #132730, without that, we'd be creating a new Thread for every blocking RwLock call on macOS.

@rustbot label +S-blocked

@rustbot rustbot added the S-blocked Status: Blocked on something else such as an RFC or other implementation work. label Nov 7, 2024
@joboet joboet added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 7, 2024
@joboet joboet linked an issue Nov 12, 2024 that may be closed by this pull request
@bors
Copy link
Contributor

bors commented Nov 20, 2024

☔ The latest upstream changes (presumably #133219) made this pull request unmergeable. Please resolve the merge conflicts.

@rustbot rustbot added O-unix Operating system: Unix-like O-windows Operating system: Windows labels Nov 25, 2024
@joboet
Copy link
Member Author

joboet commented Nov 25, 2024

I've undone the Thread move (that's probably worth another PR) and fixed some other things related to signal handling (thread::current now allocates on the main thread, so we mustn't call it in the signal handler).

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. S-blocked Status: Blocked on something else such as an RFC or other implementation work. labels Nov 25, 2024
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

joboet added a commit to joboet/rust that referenced this pull request Nov 25, 2024
If the variable does not need a destructor, `std` uses racy initialization for creating TLS keys on Windows. With just the right timing, this can lead to `TlsFree` being called. Unfortunately, with rust-lang#132654 this is hit quite often, so miri should definitely support `TlsFree` ([documentation](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-tlsfree)).

I'm filing this here instead of in the miri repo so that rust-lang#132654 isn't blocked for so long.
@joboet
Copy link
Member Author

joboet commented Nov 25, 2024

Blocked on #133457. This PR times everything just right so that the TlsFree codepath is hit in the tests.

@joboet joboet added the S-blocked Status: Blocked on something else such as an RFC or other implementation work. label Nov 25, 2024
// Safety: We only expose an opaque pointer, which maintains the `Pin` invariant.
let inner = unsafe { Pin::into_inner_unchecked(arc) };
Arc::into_raw(inner) as *const ()
pub(crate) fn with_current_name<F, R>(f: F) -> R
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is called from signal handler context, I think there should be a doc comment here explicitly saying that the function may be called from signal handler context. It seems fairly easily to accidentally break this property in the future.

use crate::ffi::{CStr, CString};
use crate::str;

/// Like a `String` it's guaranteed UTF-8 and like a `CString` it's null terminated.
pub(crate) struct ThreadNameString {
inner: CString,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this was a Cow<'static, CString>, could the main thread reference a "main\0" string literal and thus avoid a few else if main_thread::get() == Some(self.inner.id)?

Copy link
Member Author

@joboet joboet Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thread ID method has the advantage of working inside TLS destructors, so e.g. panicking in a TLS destructor on main now prints the correct name. And since we need the infrastructure anyway to detect the main thread in the first place, I figured this was better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the duplication of logic unfortunate, and IMO it'd be worth reducing.

But I am just commenting as a bystander here, hopefully I never have to dig into this code and figure out how it holds together. ;)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm planning to do a cleanup PR for this module next... it's just way to large to understand, even though the actual logic is mostly quite local.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thread ID method has the advantage of working inside TLS destructors, so e.g. panicking in a TLS destructor on main now prints the correct name. And since we need the infrastructure anyway to detect the main thread in the first place, I figured this was better.

Sorry, that scenario wasn't the issue. The actual reason why this mustn't change is that there is now nothing stopping thread::current from being called before main, which would lead to an incorrect name being stored. The ID-based detection doesn't have this issue, because the name will be correct as soon as the runtime has initialized the ID.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that is subtle. More comments would be good. :)

Comment on lines 1339 to 1371
return f(Some("main"));
}
} else if let Some(main) = main_thread::get()
&& let Some(id) = current::id::get()
&& id == main
{
return f(Some("main"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems worth some comments explaining why "main" has two separate cases.

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Nov 27, 2024
miri: implement `TlsFree`

If the variable does not need a destructor, `std` uses racy initialization for creating TLS keys on Windows. With just the right timing, this can lead to `TlsFree` being called. Unfortunately, with rust-lang#132654 this is hit quite often, so miri should definitely support `TlsFree` ([documentation](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-tlsfree)).

I'm filing this here instead of in the miri repo so that rust-lang#132654 isn't blocked for so long.
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Nov 27, 2024
Rollup merge of rust-lang#133457 - joboet:miri-tlsfree, r=saethlin

miri: implement `TlsFree`

If the variable does not need a destructor, `std` uses racy initialization for creating TLS keys on Windows. With just the right timing, this can lead to `TlsFree` being called. Unfortunately, with rust-lang#132654 this is hit quite often, so miri should definitely support `TlsFree` ([documentation](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-tlsfree)).

I'm filing this here instead of in the miri repo so that rust-lang#132654 isn't blocked for so long.
rust-lang#123550 eliminated the allocation of the main thread handle, but at the cost of greatly increased complexity. This PR proposes another approach: Instead of creating the main thread handle itself, the runtime simply remembers the thread ID of the main thread. The main thread handle is then only allocated when it is used, using the same lazy-initialization mechanism as for non-runtime use of `thread::current`, and the `name` method uses the thread ID to identify the main thread handle and return the correct name ("main") for it.

Thereby, we also allow accessing thread::current before main: as the runtime no longer tries to install its own handle, this will no longer trigger an abort. Rather, the name returned from name will only be "main" after the runtime initialization code has run, but I think that is acceptable.

This new approach also requires some changes to the signal handling code, as calling `thread::current` would now allocate when called on the main thread, which is not acceptable. I fixed this by adding a new function (`with_current_name`) that performs all the naming logic without allocation or without initializing the thread ID (which could allocate on some platforms).
@joboet joboet removed the S-blocked Status: Blocked on something else such as an RFC or other implementation work. label Nov 27, 2024
@joboet
Copy link
Member Author

joboet commented Nov 27, 2024

I've rebased this on top of #133457 and added some comments explaining everything.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
O-unix Operating system: Unix-like O-windows Operating system: Windows S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

std::thread::Thread grew from one to two pointer sizes
8 participants