-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Safe access to a #[thread_local]
should be disallowed
#17954
Comments
Another route would just be disallowing borrows as well, similar to how we only allow borrows of statics in statics. |
It could be allowed if it gave borrows something like a |
Where is the unsoundness there? |
The lifetime |
It seems like it would be really easy to add |
@alexcrichton: It also permits data races now that internal mutability in a |
Do we need |
@eddyb: It needs |
Preventing data races is a job for @eddyb |
A |
I don't think adding a new 'task lifetime is actually that helpful for thread-locals. If they are Send, and are using fork-join concurrency, 'task means something different in a child thread from a parent thread, so a child 'task might outlive a parent 'task--how do you disambiguate? Does Rust know when the new 'task lifetime is created in the child so it can reason about this soundly? If it does, and they have different lifetime names, why use 'task at all and not just a regular lifetime? On the other hand, if you can't Send them, just make them NoSend and have done with it. The only problem with making thread locals NoSend is that then you can't send them between tasks in fork-join style APIs. If you can't resolve the issues above, there's probably no sane way of determining that anyway so you might as well ban it (and really, I think it's a pretty niche usecase to want shared thread local data). I don't think semantics are a good argument here. There are lots of examples of things that aren't "semantically" 'static, like data in Rcs (which can never outlive its calling thread) that nonetheless get along without special lifetimes. |
The point of
It would be trivial to add it, and it doesn't need to know anything about other tasks.
It's possible to get a reference to the value inside the thread-local variable, and it should be considered as having a
That's not the point here. The |
There is already a way to identify 'statics that are not Send, NoSend. As far as I know, it has all the properties you're looking for. What is the purpose of adding something functionally identical? Am I missing some way in which it would or could conceivably work differently? |
It's not functionally identical. It should be possible for a thread-local |
There's already a feature gate for In C++, you just mark a global variable as |
Okay, I see your objection now, that makes sense. |
Also, the lifetime would likely be called |
Anyway, until Rust no longer has to worry about 32-bit iOS and old versions of Android it's a dead end. It doesn't really matter if it's safe or not at the moment because it's just an implementation detail for a safe library making use of it. I'm just pointing out that Rust could have TLS that's as easy to use as it is in C++11 and D rather than doing it via library hacks. |
Updated code:
|
The updated code still compiles on Code used: #![feature(thread_local)]
#[thread_local]
static FOO: usize = 3;
fn main() {
let a = &FOO;
let jg = std::thread::spawn(move || {
println!("{}", a);
});
jg.join().unwrap();
} |
Coming back to this: I think I've come to accept the simpler solution of not giving TLS variables a specific named lifetime but rather the outermost scope of the current function. That would require banning references from any other statics too. |
label: T-lang |
er, meant to add that as a tag... |
Short summary:
¹: Can’t freely use (e.g. return out of function which accesses the TLS) the reference within the thread either, though? |
So since it seems we don't want the |
I still want the semantics that make thread-local accesses like function arguments: can't let them escape. |
To clarify: IMO it's still important to be able to be able to use thread locals directly, since thread_local! does have significant overhead by comparison. |
@pythonesque do you have benchmarks to back that up? IIRC, @alexcrichton gathered various numbers that seemed to suggest the opposite, but I may be mis-remembering. |
@nikomatsakis Can confirm that I'm implementing my own channels (similar to
The difference is significant enough to be a real-world problem. |
Yes there's no debate that To actually quantify what's going on here, the I do not think we should strive to stabilize |
Ok, since
I like this idea. What would it take to implement this today, or at least what would be the first step forward? |
I'll just open a PR with my solution, it shouldn't be hard at all...
That's easy in the compiler but |
... But we could add an |
@stjepang yeah right now As for how to implement a "const expr detection" in a macro I'm not entirely sure. We could either move the implementation into the compiler (which I'd prefer to avoid) or take @arielb1's suggestion of a new macro or a variant of the current macro's syntax. For example we could "perverse" the meaning via: |
Check #[thread_local] statics correctly in the compiler. Fixes #43733 by introducing `#[allow_internal_unsafe]` analogous to `#[allow_internal_unstable]`, for letting a macro expand to `unsafe` blocks and functions even in `#![forbid(unsafe_code)]` crates. Fixes #17954 by not letting references to `#[thread_local]` statics escape the function they're taken in - we can't just use a magical lifetime because Rust has *lifetime parametrism*, so if we added the often-proposed `'thread` lifetime, we'd have no way to check it in generic code. To avoid potential edge cases in the compiler, the lifetime is actually that of a temporary at the same position, i.e. `&TLS_STATIC` has the same lifetime `&non_const_fn()` would. Referring to `#[thread_local]` `static`s at compile-time is banned now (as per PR discussion). Additionally, to remove `unsafe impl Sync` from `std::thread::local::fast::Key`, `#[thread_local]` statics are now not required to implement `Sync`, as they are not shared between threads.
Check #[thread_local] statics correctly in the compiler. Fixes #43733 by introducing `#[allow_internal_unsafe]` analogous to `#[allow_internal_unstable]`, for letting a macro expand to `unsafe` blocks and functions even in `#![forbid(unsafe_code)]` crates. Fixes #17954 by not letting references to `#[thread_local]` statics escape the function they're taken in - we can't just use a magical lifetime because Rust has *lifetime parametrism*, so if we added the often-proposed `'thread` lifetime, we'd have no way to check it in generic code. To avoid potential edge cases in the compiler, the lifetime is actually that of a temporary at the same position, i.e. `&TLS_STATIC` has the same lifetime `&non_const_fn()` would. Referring to `#[thread_local]` `static`s at compile-time is banned now (as per PR discussion). Additionally, to remove `unsafe impl Sync` from `std::thread::local::fast::Key`, `#[thread_local]` statics are now not required to implement `Sync`, as they are not shared between threads.
Thanks @eddyb for fixing this issue! Can we create a new issue for the optimization part (thread locals initialized by a constant expression should bypass the "am I initialized" check on every access)? |
@stjepang Sure, but I'm not sure how to do that cleanly to be honest. |
Update rustc-hash to version 2 This brings in the new optimized algorithm that was shown to have small performance benefits for rustc. I haven't run the rust-analyzer benchmarks. See rust-lang/rustc-hash#37.
Today this code compiles just fine, but it probably shouldn't:
I'm not nominating this because
thread_local
is behind a feature gate, just wanted an issue to track it.The text was updated successfully, but these errors were encountered: