Chromium Code Reviews| Index: base/threading/thread_local_storage.cc |
| diff --git a/base/threading/thread_local_storage.cc b/base/threading/thread_local_storage.cc |
| index 004d0a06cdce9dc6e68b93182c11119bbd3bad8c..15a1d5e2dbb498ea0c7b18f294b1805a7bbfe8dd 100644 |
| --- a/base/threading/thread_local_storage.cc |
| +++ b/base/threading/thread_local_storage.cc |
| @@ -12,6 +12,52 @@ |
| using base::internal::PlatformThreadLocalStorage; |
| +// Chrome Thread Local Storage (TLS) |
|
brettw
2016/10/04 03:33:57
This comment is awesome! Thanks.
robliao
2016/10/04 19:19:52
Thanks. Now I just need to figure out why this fai
robliao
2016/10/06 22:14:42
With the help of https://codereview.chromium.org/2
|
| +// |
| +// This TLS system allows Chrome to use a single OS level TLS slot process-wide, |
| +// and allows us to control the slot limits instead of being at the mercy of the |
| +// platform. To do this, Chrome TLS replicates an array commonly found in the OS |
| +// thread metadata. |
| +// |
| +// Overview: |
| +// |
| +// OS TLS Slots Per-Thread Per-Process Global |
| +// ... |
| +// [] Chrome TLS Array Chrome TLS Metadata |
| +// [] ----------> [][][][][ ][][][][] [][][][][ ][][][][] |
| +// [] | | |
| +// ... V V |
| +// Metadata Version Slot Information |
| +// Your Data! |
| +// |
| +// Using a single OS TLS slot, Chrome TLS allocates an array on demand for the |
| +// lifetime of each thread that requests Chrome TLS data. Each per-thread TLS |
| +// array matches the length of the per-process global metadata array. |
| +// |
| +// A per-process global TLS metadata array tracks information about each item in |
| +// the per-thread array: |
| +// * Status: Tracks if the slot is allocated or free to assign. |
| +// * Destructor: An optional destructor to call on thread destruction for that |
| +// specific slot. |
| +// * Version: Tracks the current version of the TLS slot. Each TLS slot |
| +// allocation is associated with a unique version number. |
| +// |
| +// Most OS TLS APIs guarantee that a newly allocated TLS slot is |
| +// initialized to 0 for all threads. The Chrome TLS system provides |
| +// this guarantee by tracking the version for each TLS slot here |
| +// on each per-thread Chrome TLS array entry. Threads that access |
| +// a slot with a mismatched version will receive 0 as their value. |
| +// The metadata version is incremented when the client frees a |
| +// slot. The per-thread metadata version is updated when a client |
| +// writes to the slot. This scheme allows for constant time |
| +// invalidation and avoids the need to iterate through each Chrome |
| +// TLS array to mark the slot as zero. |
| +// |
| +// Just like an OS TLS API, clients of the Chrome TLS are responsible for |
| +// managing any necessary lifetime of the data in their slots. The only |
| +// convenience provided is automatic destruction when a thread ends. If a client |
| +// frees a slot, that client is responsible for destroying the data in the slot. |
| + |
| namespace { |
| // In order to make TLS destructors work, we need to keep around a function |
| // pointer to the destructor for each slot. We keep this array of pointers in a |
| @@ -36,6 +82,12 @@ enum TlsStatus { |
| struct TlsMetadata { |
| TlsStatus status; |
| base::ThreadLocalStorage::TLSDestructorFunc destructor; |
| + uint32_t version; |
| +}; |
| + |
| +struct TlsVectorEntry { |
| + void* data; |
| + uint32_t version; |
| }; |
| // This LazyInstance isn't needed until after we've constructed the per-thread |
| @@ -54,7 +106,7 @@ constexpr int kMaxDestructorIterations = kThreadLocalStorageSize; |
| // recursively depend on this initialization. |
| // As a result, we use Atomics, and avoid anything (like a singleton) that might |
| // require memory allocations. |
| -void** ConstructTlsVector() { |
| +TlsVectorEntry* ConstructTlsVector() { |
| PlatformThreadLocalStorage::TLSKey key = |
| base::subtle::NoBarrier_Load(&g_native_tls_key); |
| if (key == PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES) { |
| @@ -96,21 +148,20 @@ void** ConstructTlsVector() { |
| // allocated vector, so that we don't have dependence on our allocator until |
| // our service is in place. (i.e., don't even call new until after we're |
| // setup) |
| - void* stack_allocated_tls_data[kThreadLocalStorageSize]; |
| + TlsVectorEntry stack_allocated_tls_data[kThreadLocalStorageSize]; |
| memset(stack_allocated_tls_data, 0, sizeof(stack_allocated_tls_data)); |
| // Ensure that any rentrant calls change the temp version. |
| PlatformThreadLocalStorage::SetTLSValue(key, stack_allocated_tls_data); |
| // Allocate an array to store our data. |
| - void** tls_data = new void*[kThreadLocalStorageSize]; |
| + TlsVectorEntry* tls_data = new TlsVectorEntry[kThreadLocalStorageSize]; |
| memcpy(tls_data, stack_allocated_tls_data, sizeof(stack_allocated_tls_data)); |
| PlatformThreadLocalStorage::SetTLSValue(key, tls_data); |
| return tls_data; |
| } |
| -void OnThreadExitInternal(void* value) { |
| - DCHECK(value); |
| - void** tls_data = static_cast<void**>(value); |
| +void OnThreadExitInternal(TlsVectorEntry* tls_data) { |
| + DCHECK(tls_data); |
| // Some allocators, such as TCMalloc, use TLS. As a result, when a thread |
| // terminates, one of the destructor calls we make may be to shut down an |
| // allocator. We have to be careful that after we've shutdown all of the known |
| @@ -120,7 +171,7 @@ void OnThreadExitInternal(void* value) { |
| // allocated vector, so that we don't have dependence on our allocator after |
| // we have called all g_tls_metadata destructors. (i.e., don't even call |
| // delete[] after we're done with destructors.) |
| - void* stack_allocated_tls_data[kThreadLocalStorageSize]; |
| + TlsVectorEntry stack_allocated_tls_data[kThreadLocalStorageSize]; |
| memcpy(stack_allocated_tls_data, tls_data, sizeof(stack_allocated_tls_data)); |
| // Ensure that any re-entrant calls change the temp version. |
| PlatformThreadLocalStorage::TLSKey key = |
| @@ -146,15 +197,16 @@ void OnThreadExitInternal(void* value) { |
| // then we'll iterate several more times, so it is really not that critical |
| // (but it might help). |
| for (int slot = 0; slot < kThreadLocalStorageSize ; ++slot) { |
| - void* tls_value = stack_allocated_tls_data[slot]; |
| - if (!tls_value || tls_metadata[slot].status == TlsStatus::FREE) |
| + void* tls_value = stack_allocated_tls_data[slot].data; |
| + if (!tls_value || tls_metadata[slot].status == TlsStatus::FREE || |
| + stack_allocated_tls_data[slot].version != tls_metadata[slot].version) |
| continue; |
| base::ThreadLocalStorage::TLSDestructorFunc destructor = |
| tls_metadata[slot].destructor; |
| if (!destructor) |
| continue; |
| - stack_allocated_tls_data[slot] = nullptr; // pre-clear the slot. |
| + stack_allocated_tls_data[slot].data = nullptr; // pre-clear the slot. |
| destructor(tls_value); |
| // Any destructor might have called a different service, which then set a |
| // different slot to a non-null value. Hence we need to check the whole |
| @@ -187,11 +239,11 @@ void PlatformThreadLocalStorage::OnThreadExit() { |
| // Maybe we have never initialized TLS for this thread. |
| if (!tls_data) |
| return; |
| - OnThreadExitInternal(tls_data); |
| + OnThreadExitInternal(static_cast<TlsVectorEntry*>(tls_data)); |
| } |
| #elif defined(OS_POSIX) |
| void PlatformThreadLocalStorage::OnThreadExit(void* value) { |
| - OnThreadExitInternal(value); |
| + OnThreadExitInternal(static_cast<TlsVectorEntry*>(value)); |
| } |
| #endif // defined(OS_WIN) |
| @@ -207,6 +259,7 @@ void ThreadLocalStorage::StaticSlot::Initialize(TLSDestructorFunc destructor) { |
| // Grab a new slot. |
| slot_ = kInvalidSlotValue; |
| + version_ = 0; |
| { |
| base::AutoLock auto_lock(g_tls_metadata_lock.Get()); |
| for (int i = 0; i < kThreadLocalStorageSize; ++i) { |
| @@ -222,6 +275,7 @@ void ThreadLocalStorage::StaticSlot::Initialize(TLSDestructorFunc destructor) { |
| g_tls_metadata[slot_candidate].destructor = destructor; |
| g_last_assigned_slot = slot_candidate; |
| slot_ = slot_candidate; |
| + version_ = g_tls_metadata[slot_candidate].version; |
| break; |
| } |
| } |
| @@ -240,31 +294,36 @@ void ThreadLocalStorage::StaticSlot::Free() { |
| base::AutoLock auto_lock(g_tls_metadata_lock.Get()); |
| g_tls_metadata[slot_].status = TlsStatus::FREE; |
| g_tls_metadata[slot_].destructor = nullptr; |
| + ++(g_tls_metadata[slot_].version); |
| } |
| slot_ = kInvalidSlotValue; |
| base::subtle::Release_Store(&initialized_, 0); |
| } |
| void* ThreadLocalStorage::StaticSlot::Get() const { |
| - void** tls_data = static_cast<void**>( |
| + TlsVectorEntry* tls_data = static_cast<TlsVectorEntry*>( |
| PlatformThreadLocalStorage::GetTLSValue( |
| base::subtle::NoBarrier_Load(&g_native_tls_key))); |
| if (!tls_data) |
| tls_data = ConstructTlsVector(); |
| DCHECK_NE(slot_, kInvalidSlotValue); |
| DCHECK_LT(slot_, kThreadLocalStorageSize); |
| - return tls_data[slot_]; |
| + // Version mismatches means this slot was previously freed. |
| + if (tls_data[slot_].version != version_) |
| + return nullptr; |
| + return tls_data[slot_].data; |
| } |
| void ThreadLocalStorage::StaticSlot::Set(void* value) { |
| - void** tls_data = static_cast<void**>( |
| + TlsVectorEntry* tls_data = static_cast<TlsVectorEntry*>( |
| PlatformThreadLocalStorage::GetTLSValue( |
| base::subtle::NoBarrier_Load(&g_native_tls_key))); |
| if (!tls_data) |
| tls_data = ConstructTlsVector(); |
| DCHECK_NE(slot_, kInvalidSlotValue); |
| DCHECK_LT(slot_, kThreadLocalStorageSize); |
| - tls_data[slot_] = value; |
| + tls_data[slot_].data = value; |
| + tls_data[slot_].version = version_; |
| } |
| ThreadLocalStorage::Slot::Slot(TLSDestructorFunc destructor) { |