-
Notifications
You must be signed in to change notification settings - Fork 13k
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
unsafe: ScopedKey allows for Sync-ification of non-Sync data #25894
Comments
This seems concerning: this is designed to be thread-local, so that e.g. storing a |
Qualifying the impl with |
I believe the only way
The main issue is actually within the libstd TLS implementation:
Should |
"I believe the only way #[thread_local] statics can be safe is by requiring the absence of This is wrong, I think. For example, one might use thread local data for intrusive linked lists of hazard pointers. They would be thread local but might still be |
Based on what @eddyb wrote, it seems like the TLS interface requires a One idea I had of how to enable this in todays Rust is to make
According to @eddyb, this could also help with |
@Kimundi I was expecting the @pythonesque You would still be about to use the TLS API provided by libstd with thread-safe data, it's just the low-level Ideas of thread-local lifetimes predate the removal of Actually, there's an... easy solution for this. Somehow we were blinded by language semantics, but their details are fluid. That would ensure that the the thread is just as alive for such borrows as it is for stack ones. |
Wouldn't that limit the usability of thread locals in some cases, though? e.g., wouldn't that make it difficult to have a thread local point to another thread local? Maybe I just have to think about it. (BTW, I'm aware we're just talking about the low-level API :)). |
@pythonesque in a We could disallow Which makes me think, as a compromise between a They would behave as if they were borrowed before everything else in the function where they're used, but still not |
There's a few intertwined issues here at play, so I just want to quickly summarize the way I see things:
I'm not sure I follow why we might require the types in a |
triage: P-medium This is an unstable API so it's not super-high priority to fix, but we should defnitely fix! |
@alexcrichton requiring that a I've changed my mind and prefer an rvalue borrow semantic for If Maybe a combined strategy, where borrows of |
Right, but returning a borrow with a |
@alexcrichton how can it be abused without sending it to a different thread? |
The actual values themselves do not have a |
Currently the compiler has no knowledge of `#[thread_local]` which forces users to take on two burdens of unsafety: * The lifetime of the borrow of a `#[thread_local]` static is **not** `'static` * Types in `static`s are required to be `Sync` The thread-local modules mostly curb these facets of unsafety by only allowing very limited scopes of borrows as well as allowing all types to be stored in a thread-local key (regardless of whether they are `Sync`) through an `unsafe impl`. Unfortunately these measures have the consequence of being able to take the address of the key itself and send it to another thread, allowing the same key to be accessed from two different threads. This is clearly unsafe, and this commit fixes this problem with the same trick used by `LocalKey`, which is to have an indirect function call to find the address of the *current thread's* thread local. This way the address of thread local keys can safely be sent among threads as their lifetime truly is `'static`. This commit will reduce the performance of cross-crate scoped thread locals as it now requires an indirect function call, but this can likely be overcome in a future commit. Closes rust-lang#25894
Currently the compiler has no knowledge of `#[thread_local]` which forces users to take on two burdens of unsafety: * The lifetime of the borrow of a `#[thread_local]` static is **not** `'static` * Types in `static`s are required to be `Sync` The thread-local modules mostly curb these facets of unsafety by only allowing very limited scopes of borrows as well as allowing all types to be stored in a thread-local key (regardless of whether they are `Sync`) through an `unsafe impl`. Unfortunately these measures have the consequence of being able to take the address of the key itself and send it to another thread, allowing the same key to be accessed from two different threads. This is clearly unsafe, and this commit fixes this problem with the same trick used by `LocalKey`, which is to have an indirect function call to find the address of the *current thread's* thread local. This way the address of thread local keys can safely be sent among threads as their lifetime truly is `'static`. This commit will reduce the performance of cross-crate scoped thread locals as it now requires an indirect function call, but this can likely be overcome in a future commit. Closes rust-lang#25894
Currently the compiler has no knowledge of `#[thread_local]` which forces users to take on two burdens of unsafety: * The lifetime of the borrow of a `#[thread_local]` static is **not** `'static` * Types in `static`s are required to be `Sync` The thread-local modules mostly curb these facets of unsafety by only allowing very limited scopes of borrows as well as allowing all types to be stored in a thread-local key (regardless of whether they are `Sync`) through an `unsafe impl`. Unfortunately these measures have the consequence of being able to take the address of the key itself and send it to another thread, allowing the same key to be accessed from two different threads. This is clearly unsafe, and this commit fixes this problem with the same trick used by `LocalKey`, which is to have an indirect function call to find the address of the *current thread's* thread local. This way the address of thread local keys can safely be sent among threads as their lifetime truly is `'static`. This commit will reduce the performance of cross-crate scoped thread locals as it now requires an indirect function call, but this can likely be overcome in a future commit. Closes #25894
There is an
unsafe impl<T> Sync for KeyInner<T> { }
in the scoped TLS module, which allows for sharing of data that wasn't meant to be shared.An unqualified
unsafe impl<T> Sync for Foo { }
should only be used whenFoo
provides some sort of threadsafe locking mechanism.KeyInner
andScopedKey
do not.This lets us, for example, clone an
Rc
orRefCell
between threads, in safe code:playpen
Here, we have
Rc
clones from different threads being interleaved, which can cause unsafety if there's a race on the refcount. We could also cause unsafety with aRefCell
, or any other non-Sync type.ScopedKey
basically lets us share&T
across threads even ifT: !Sync
. I put the sleeps in there because somehow without them the routines are run serially.Note that this is not a problem with the
scoped()
API. I'm usingscoped()
here because to exploit&T: Send, T:!Sync
one needs to be able to share across threads, andArc
has an additionalSend
bound whichScopedKey
doesn't satisfy. Any usablescoped
API will probably have to make the assumption that&T: Send iff T:Sync
. Nor is this a problem withRc
, since it can be done withRefCell
, too.Solution: Make it
unsafe impl<T: Sync> Sync for KeyInner<T> { }
... I think. I'm not familiar with this API, and I'm not sure why it's even
Sync
in the first place.The text was updated successfully, but these errors were encountered: