-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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::thread::LocalKeyState: Add state Initializing #43550
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @sfackler (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
Thanks for the PR! Could the formatting related changes be removed from this PR for now? |
@alexcrichton I'm not sure how to do that other than to remove them manually - is there a standard |
@joshlf can you disable auto-rustfmt in your editor, redo the changes, and resubmit them? |
@alexcrichton Done. |
I think it'd be good to modify |
OK, should be ready to go. Removing the WIP tag. |
|
@eddyb Can you take a quick look at this and let me know if you think anything here conflicts with your PR? I don't think it does, but you know your PR better than I do, and I suspect yours will get merged first so I'll end up rebasing on top of it. I suspect that all I'll need to do is mark some functions and methods |
This is a lot of code. But I think all you have to check for if: if you take |
OK sounds good; thanks! |
1ca7c0a
to
06834a9
Compare
Looks like one of the CI tests failed due to network errors. Does anybody have the ability to tell Travis to retry? Looks like I don't.
|
☔ The latest upstream changes (presumably #43746) made this pull request unmergeable. Please resolve the merge conflicts. |
Just do a trivial |
546984e
to
bc6800a
Compare
@eddyb Do you have any advice for how to get the lifetimes to work in this PR (in particular, in the The current version of the code won't compile because |
@joshlf The one function I had to deal with had the same problem, btw - look at how I changed |
☔ The latest upstream changes (presumably #43710) made this pull request unmergeable. Please resolve the merge conflicts. |
@joshlf Btw you should use |
@eddyb Do you know if there's a way to |
@joshlf You can run it now and it should kill merge commits. |
19d4391
to
a96d10d
Compare
Hey @alexcrichton , can I ask you a question about some code that unsafe extern fn destroy_value<T: 'static>(ptr: *mut u8) {
// The OS TLS ensures that this key contains a NULL value when this
// destructor starts to run. We set it back to a sentinel value of 1 to
// ensure that any future calls to `get` for this thread will return
// `None`.
//
// Note that to prevent an infinite loop we reset it back to null right
// before we return from the destructor ourselves.
let ptr = Box::from_raw(ptr as *mut Value<T>);
let key = ptr.key;
key.os.set(1 as *mut u8);
drop(ptr);
key.os.set(ptr::null_mut());
} When you set the pointer back to null before returning, doesn't that make it so that a future access of the key after the destructor has run will cause the key to be re-initialized because a null pointer looks like an uninitialized key rather than a destroyed one? This could happen if the destructor of another TLS key accesses this key, and that destructor is called after this key's destructor. |
@joshlf It's tri-state, IIRC they go like this (conceptually, the actual types might differ):
|
@eddyb I believe you're right, but my concern is what happens after the destruction has finished (after which, as the comment that I quoted says, the pointer is set back from 1 to 0). The implementation of pub unsafe fn get(&'static self) -> Option<&'static UnsafeCell<Option<T>>> {
let ptr = self.os.get() as *mut Value<T>;
if !ptr.is_null() {
if ptr as usize == 1 {
return None
}
return Some(&(*ptr).value);
}
// If the lookup returned null, we haven't initialized our own
// local copy, so do that now.
let ptr: Box<Value<T>> = box Value {
key: self,
value: UnsafeCell::new(None),
};
let ptr = Box::into_raw(ptr);
self.os.set(ptr as *mut u8);
Some(&(*ptr).value)
} |
6eed19e
to
7635f72
Compare
Indeed! We don't have much of an option here, though, unless we track this data elsewhere. If we leave it as 1 then the OS will continue to call the destructor function rather than returning. |
OK, in that case I think I'll clarify that in the documentation, and add a TODO in case somebody comes along and figures out how to fix it. EDIT: I see now that this is already documented in the doc comment on |
1a14f08
to
1dbbfd6
Compare
OK, it's ready for review. Just to recap:
|
☔ The latest upstream changes (presumably #43931) made this pull request unmergeable. Please resolve the merge conflicts. |
if let &LocalKeyValue::Valid(ref inner) = &*slot.get() { | ||
// Do this in a separate if branch (rather than just part of the | ||
// match statement in the else block) to increase the performance | ||
// of the fast path. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this measurably help the codegen?
I'd naively expect not, butI'm curious if you have benchmarks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I'm actually not sure. I tried benchmarking it, but I'm not confident that my xargo
command is testing what I think it is because I ran it with a version of the code that had a compiler error, and it still ran properly, so I'm not sure what artifact xargo
is picking up to link as libstd. Here's what I'm running:
RUST_BACKTRACE=1 RUSTFLAGS='-C opt-level=3' XARGO_RUST_SRC=/path/to/rust/src/ xargo run --release
Given this command, it looks like there's not much of a difference, but I'm skeptical for the reasons mentioned. If you want to give it a try yourself (since I assume you're better at this whole build pipeline thing), it would be give me more confidence.
|
||
// Register a destructor for the value. register_dtor should be called | ||
// after the key has been transitioned into the Valid state. | ||
register_dtor: unsafe fn(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't this still happen on the first call to get
? If initialization fails that means we'll just run the destructor on a noop value, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to avoid adding an extra branch in the hot path, and that would require branching in get
to see whether the destructor had been registered already.
src/libstd/thread/local.rs
Outdated
// Set inner to Destroyed before we drop tmp so that a | ||
// recursive call to get (called from the destructor) will | ||
// be able to detect that the value is already being dropped. | ||
ptr::write((*ptr).inner.get(), __LocalKeyValue::Destroyed); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think mem::replace
can be used here instead of read/write?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call.
src/libstd/thread/local.rs
Outdated
let ptr = self.os.get() as *mut Value<T>; | ||
if !ptr.is_null() { | ||
if ptr as usize == 1 { | ||
return None | ||
// The destructor was already called (and set self.os to 1). | ||
return &*(&self.dummy_destroyed as *const _); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How come get
no longer takes 'static self
to require these casts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, honestly I think I wasn't sure what to do there so I just copied what mod fast
does, but of course that doesn't make sense because mod os
uses a truly static value. I'll change it back.
1dbbfd6
to
523f8a5
Compare
523f8a5
to
fe1894a
Compare
Hm ok so looking into the perf impact here, I'm getting more and more wary about this change... Right now the Today, however, calls to In other words, I think that fundamentally adding an "initializing" state is going to come at a perf hit to all thread locals. With that in mind, I wonder if there are perhaps other methods to tackle this for your use case? I've always imagined that |
Can't pub fn with<F, R>(&'static self, f: F) -> R
where F: FnOnce(&T) -> R {
self.try_with(f).expect("cannot access a TLS value during or \
after it is destroyed")
}
I already decided to give up on making this work for allocation (see this comment). However, this is still useful for other recursive initialization dependencies. |
I commented awhile ago with this example which generates this IR for a In general yes,
In that case I'm somewhat inclined to close this? This PR isn't needed for you use case, is technically a regression in behavior, and is currently a regression in performance. |
OK, fair enough. I'll open a PR to clarify that TLS constructors cannot depend recursively on the values that they construct, but other than the case of allocators (and perhaps some weird dependency injection scenarios), this should only arise within a given crate, in which case it's more reasonable to ask authors to just architect their stuff differently. |
PR added: #44396 |
Ok, closing in favor of #44396 in that case |
Closes #43491.
I know that the issue hasn't been discussed or approved yet, but I figured I'd make this PR to provide something concrete for the discussion.
Open question: This is my first serious PR, and I'm not sure what to do about my
rustfmt
automatically re-formatting existing code that I didn't change. Somebody's guidance here would be appreciated.