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

RFC: Add a thread local storage module, std::tls #461

Merged
merged 3 commits into from
Nov 21, 2014

Conversation

alexcrichton
Copy link
Member

Introduce a new thread local storage module to the standard library, std::tls,
providing:

  • Scoped TLS, a non-owning variant of TLS for any value.
  • Owning TLS, an owning, dynamically initialized, dynamically destructed
    variant, similar to std::local_data today.

Rendered

Introduce a new thread local storage module to the standard library, `std::tls`,
providing:

* Scoped TLS, a non-owning variant of TLS for any value.
* Owning TLS, an owning, dynamically initialized, dynamically destructed
  variant, similar to `std::local_data` today.
lost when writing Unix-only code.

Destructor support for Windows will be provided through a custom implementation
of tracking known destructors for TLS keys.

Choose a reason for hiding this comment

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

On Windows, you can set a callback for when a thread exits, and you can then iterate over all TLS keys and destroy them if they are set.

See https://github.com/ChromiumWebApps/chromium/blob/master/base/threading/thread_local_storage_win.cc#L42 for how to do it without using DllMain.

Iterating over all possible TLS keys is quadratic instead of linear if they are sparsely used, but it should be possible to write code that directly accesses the Windows TEB to avoid this if desired.

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed! I ran across that same trick when whipping up the sample implementation. I definitely agree that the implementation can be improved as well!

@aturon
Copy link
Member

aturon commented Nov 13, 2014

cc @thestinger, would be good to get your feedback on this proposal.


### Destructor support

The major difference between Unix and Windows TLS support is that Unix supports

Choose a reason for hiding this comment

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

It's also possible to use only #[thread_local] with no secondary pthread_key_create / synchronization by using C++11 destructor support. On Linux, glibc defines a fn __cxa_thread_atexit_impl(dtor: unsafe extern "C" fn(ptr: *mut c_void), ptr: *mut c_void, dso_symbol: *mut i8) where dso_symbol can just be retrived by defining static mut __dso_handle: i8. On OS X, there's a fn _tlv_atexit(dtor: unsafe extern "C" fn(ptr: *mut c_void), ptr: *mut c_void); function. Rust could use the weak symbol trick to call these when available and fall back to a crappier implementation on top of dynamic TLS.

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed! If you take a look at the sample implementation you'll see that it does precisely that!

@brson
Copy link
Contributor

brson commented Nov 18, 2014

ISTM like scoped TLS may be the 'preferred' way to use TLS as it is more structured, harder to create error-prone access patterns, but this design gives traditional tls the better module name.

@brson
Copy link
Contributor

brson commented Nov 18, 2014

This RFC doesn't seem to explain how it achieves acceptable performance compared to local_data and #[thread_local], which is one of the main concerns.

@alexcrichton
Copy link
Member Author

@brson that's a good point about naming, we could take no preference at all and perhaps move everything in to std::tls as well with DynamicKey and ScopedKey with dynamic_tls! and scoped_tls! macros. I think I'm somewhat biased towards "owned by default" (and so is the repo), but that's also probably because it's all we have today.

The sample implementation's benchmarking numbers are currently all on par with #[thread_local] (numbers are in some of the above comments), but I'll try to call it out specifically how this is trying to mirror the system primitives as much as possible

Today I also discovered an unsoundness with this API as proposed, specifically with the get method on owning TLS keys. The return Ref<T> satisfies the 'static bound, allowing you to store references to other TLS variables in TLS itself, perhaps causing undefined behavior during destructors. Which is to say that TLS variables can store stale references to one another:

#![feature(phase)]

#[phase(plugin, link)]
extern crate tls;

use std::cell::RefCell;

dynamic_tls!(static FOO: Box<int> = box 1)
dynamic_tls!(static BAR: RefCell<Option<tls::dynamic::Ref<Box<int>>>> = RefCell::new(None))

fn main() {
    *BAR.get().unwrap().borrow_mut() = Some(FOO.get().unwrap());
}

What this really wants to do is to store a &'thread T inside of Ref<T> to ensure it doesn't satisfy 'static and can't be stored in another global itself. I've been talking with @nikomatsakis on IRC about this and it may be possible, but it would prevent this from landing in the interim. For that reason I'm going to modify the get() api to with() so it has the same semantics as scoped tls.

@jnicklas
Copy link

When I hear TLS I think "Transport Layer Security", not "Thread Local Storage". Wouldn't it make more sense to call this std::thread_local or something?

@reem
Copy link

reem commented Nov 20, 2014

@alexcrichton Could &'thread T be emulated with:

struct ThreadRef<T> {
   marker: NoSend,
   data: &'static T
}

and the appropriate Deref impls?

@alexcrichton
Copy link
Member Author

@reem I thought so! (and I was really hoping so). The catch is that the 'static > 'thread (note >, not >=). It should be the case that &'thread T does not satisfy 'static, but the struct you've shown sadly does. The details are in this comment above.

bors added a commit to rust-lang/rust that referenced this pull request Nov 21, 2014
This PR completes the removal of the runtime system and green-threaded abstractions as part of implementing [RFC 230](rust-lang/rfcs#230).

Specifically:

* It removes the `Runtime` trait, welding the scheduling infrastructure directly to native threads.

* It removes `libgreen` and `libnative` entirely.

* It rewrites `sync::mutex` as a trivial layer on top of native mutexes. Eventually, the two modules will be merged.

* It hides the vast majority of `std::rt`.

This completes the basic task of removing the runtime system (I/O and scheduling) and components that depend on it. 

After this lands, a follow-up PR will pull the `rustrt` crate back into `std`, turn `std::task` into `std::thread` (with API changes to go along with it), and completely cut out the remaining startup/teardown sequence. Other changes, including new [TLS](rust-lang/rfcs#461) and synchronization are in the RFC or pre-RFC phase.

Closes #17325
Closes #18687

[breaking-change]

r? @alexcrichton
@aturon aturon merged commit 4fc3b6d into rust-lang:master Nov 21, 2014
@aturon
Copy link
Member

aturon commented Nov 21, 2014

RFC accepted.
Discussion
Tracking

@reem
Copy link

reem commented Nov 21, 2014

@alexcrichton it looks like the simplest solution would just be to add a NoStatic marker - that seems like it may be generally useful to unsafe code.

alexcrichton added a commit to alexcrichton/rust that referenced this pull request Nov 21, 2014
This commit removes the `std::local_data` module in favor of a new
`std::thread_local` module providing thread local storage. The module provides
two variants of TLS: one which owns its contents and one which is based on
scoped references. Each implementation has pros and cons listed in the
documentation.

Both flavors have accessors through a function called `with` which yield a
reference to a closure provided. Both flavors also panic if a reference cannot
be yielded and provide a function to test whether an access would panic or not.
This is an implementation of [RFC 461][rfc] and full details can be found in
that RFC.

This is a breaking change due to the removal of the `std::local_data` module.
All users can migrate to the new thread local system like so:

    thread_local!(static FOO: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None)))

The old `local_data` module inherently contained the `Rc<RefCell<Option<T>>>` as
an implementation detail which must now be explicitly stated by users.

[rfc]: rust-lang/rfcs#461
[breaking-change]
alexcrichton added a commit to alexcrichton/rust that referenced this pull request Nov 22, 2014
This commit removes the `std::local_data` module in favor of a new `std::thread_local`
module providing thread local storage. The module provides two variants of TLS:
one which owns its contents and one which is based on scoped references. Each
implementation has pros and cons listed in the documentation.

Both flavors have accessors through a function called `with` which yield a
reference to a closure provided. Both flavors also panic if a reference cannot
be yielded and provide a function to test whether an access would panic or not.
This is an implementation of [RFC 461][rfc] and full details can be found in
that RFC.

This is a breaking change due to the removal of the `std::local_data` module.
All users can migrate to the new tls system like so:

    thread_local!(static FOO: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None)))

The old `local_data` module inherently contained the `Rc<RefCell<Option<T>>>` as
an implementation detail which must now be explicitly stated by users.

[rfc]: rust-lang/rfcs#461
[breaking-change]
alexcrichton added a commit to alexcrichton/rust that referenced this pull request Nov 22, 2014
This commit removes the `std::local_data` module in favor of a new
`std::thread_local` module providing thread local storage. The module provides
two variants of TLS: one which owns its contents and one which is based on
scoped references. Each implementation has pros and cons listed in the
documentation.

Both flavors have accessors through a function called `with` which yield a
reference to a closure provided. Both flavors also panic if a reference cannot
be yielded and provide a function to test whether an access would panic or not.
This is an implementation of [RFC 461][rfc] and full details can be found in
that RFC.

This is a breaking change due to the removal of the `std::local_data` module.
All users can migrate to the new thread local system like so:

    thread_local!(static FOO: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None)))

The old `local_data` module inherently contained the `Rc<RefCell<Option<T>>>` as
an implementation detail which must now be explicitly stated by users.

[rfc]: rust-lang/rfcs#461
[breaking-change]
bors added a commit to rust-lang/rust that referenced this pull request Nov 22, 2014
This commit removes the `std::local_data` module in favor of a new `std::thread_local`
module providing thread local storage. The module provides two variants of TLS:
one which owns its contents and one which is based on scoped references. Each
implementation has pros and cons listed in the documentation.

Both flavors have accessors through a function called `with` which yield a
reference to a closure provided. Both flavors also panic if a reference cannot
be yielded and provide a function to test whether an access would panic or not.
This is an implementation of [RFC 461][rfc] and full details can be found in
that RFC.

This is a breaking change due to the removal of the `std::local_data` module.
All users can migrate to the new tls system like so:

    thread_local!(static FOO: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None)))

The old `local_data` module inherently contained the `Rc<RefCell<Option<T>>>` as
an implementation detail which must now be explicitly stated by users.

[rfc]: rust-lang/rfcs#461
[breaking-change]
alexcrichton added a commit to alexcrichton/rust that referenced this pull request Nov 24, 2014
This commit removes the `std::local_data` module in favor of a new
`std::thread_local` module providing thread local storage. The module provides
two variants of TLS: one which owns its contents and one which is based on
scoped references. Each implementation has pros and cons listed in the
documentation.

Both flavors have accessors through a function called `with` which yield a
reference to a closure provided. Both flavors also panic if a reference cannot
be yielded and provide a function to test whether an access would panic or not.
This is an implementation of [RFC 461][rfc] and full details can be found in
that RFC.

This is a breaking change due to the removal of the `std::local_data` module.
All users can migrate to the new thread local system like so:

    thread_local!(static FOO: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None)))

The old `local_data` module inherently contained the `Rc<RefCell<Option<T>>>` as
an implementation detail which must now be explicitly stated by users.

[rfc]: rust-lang/rfcs#461
[breaking-change]
bors added a commit to rust-lang/rust that referenced this pull request Nov 24, 2014
This commit removes the `std::local_data` module in favor of a new `std::thread_local`
module providing thread local storage. The module provides two variants of TLS:
one which owns its contents and one which is based on scoped references. Each
implementation has pros and cons listed in the documentation.

Both flavors have accessors through a function called `with` which yield a
reference to a closure provided. Both flavors also panic if a reference cannot
be yielded and provide a function to test whether an access would panic or not.
This is an implementation of [RFC 461][rfc] and full details can be found in
that RFC.

This is a breaking change due to the removal of the `std::local_data` module.
All users can migrate to the new tls system like so:

    thread_local!(static FOO: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None)))

The old `local_data` module inherently contained the `Rc<RefCell<Option<T>>>` as
an implementation detail which must now be explicitly stated by users.

[rfc]: rust-lang/rfcs#461
[breaking-change]
cuviper pushed a commit to cuviper/rayon that referenced this pull request Mar 28, 2017
This commit removes the `std::local_data` module in favor of a new
`std::thread_local` module providing thread local storage. The module provides
two variants of TLS: one which owns its contents and one which is based on
scoped references. Each implementation has pros and cons listed in the
documentation.

Both flavors have accessors through a function called `with` which yield a
reference to a closure provided. Both flavors also panic if a reference cannot
be yielded and provide a function to test whether an access would panic or not.
This is an implementation of [RFC 461][rfc] and full details can be found in
that RFC.

This is a breaking change due to the removal of the `std::local_data` module.
All users can migrate to the new thread local system like so:

    thread_local!(static FOO: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None)))

The old `local_data` module inherently contained the `Rc<RefCell<Option<T>>>` as
an implementation detail which must now be explicitly stated by users.

[rfc]: rust-lang/rfcs#461
[breaking-change]
@Centril Centril added the A-thread-local Proposals relating to thread local storage (TLS). label Nov 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-thread-local Proposals relating to thread local storage (TLS).
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants