// Copyright 2012 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/threading/thread_local_storage.h" #include "base/memory/raw_ptr.h" #include "base/no_destructor.h" #include "base/threading/simple_thread.h" #include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" #if BUILDFLAG(IS_WIN) #include #include // Ignore warnings about ptr->int conversions that we use when // storing ints into ThreadLocalStorage. #pragma warning(disable : 4311 4312) #endif namespace base { #if BUILDFLAG(IS_POSIX) namespace internal { // This class is friended by ThreadLocalStorage. class ThreadLocalStorageTestInternal { public: static bool HasBeenDestroyed() { return ThreadLocalStorage::HasBeenDestroyed(); } }; } // namespace internal #endif // BUILDFLAG(IS_POSIX) namespace { const int kInitialTlsValue = 0x5555; const int kFinalTlsValue = 0x7777; // How many times must a destructor be called before we really are done. const int kNumberDestructorCallRepetitions = 3; void ThreadLocalStorageCleanup(void* value); ThreadLocalStorage::Slot& TLSSlot() { static NoDestructor slot( &ThreadLocalStorageCleanup); return *slot; } class ThreadLocalStorageRunner : public DelegateSimpleThread::Delegate { public: explicit ThreadLocalStorageRunner(int* tls_value_ptr) : tls_value_ptr_(tls_value_ptr) {} ThreadLocalStorageRunner(const ThreadLocalStorageRunner&) = delete; ThreadLocalStorageRunner& operator=(const ThreadLocalStorageRunner&) = delete; ~ThreadLocalStorageRunner() override = default; void Run() override { *tls_value_ptr_ = kInitialTlsValue; TLSSlot().Set(tls_value_ptr_); int* ptr = static_cast(TLSSlot().Get()); EXPECT_EQ(ptr, tls_value_ptr_); EXPECT_EQ(*ptr, kInitialTlsValue); *tls_value_ptr_ = 0; ptr = static_cast(TLSSlot().Get()); EXPECT_EQ(ptr, tls_value_ptr_); EXPECT_EQ(*ptr, 0); *ptr = kFinalTlsValue + kNumberDestructorCallRepetitions; } private: raw_ptr tls_value_ptr_; }; void ThreadLocalStorageCleanup(void *value) { int *ptr = static_cast(value); // Destructors should never be called with a NULL. ASSERT_NE(nullptr, ptr); if (*ptr == kFinalTlsValue) return; // We've been called enough times. ASSERT_LT(kFinalTlsValue, *ptr); ASSERT_GE(kFinalTlsValue + kNumberDestructorCallRepetitions, *ptr); --*ptr; // Move closer to our target. // Tell tls that we're not done with this thread, and still need destruction. TLSSlot().Set(value); } #if BUILDFLAG(IS_POSIX) constexpr intptr_t kDummyValue = 0xABCD; constexpr size_t kKeyCount = 20; // The order in which pthread keys are destructed is not specified by the POSIX // specification. Hopefully, of the 20 keys we create, some of them should be // destroyed after the TLS key is destroyed. class UseTLSDuringDestructionRunner { public: UseTLSDuringDestructionRunner() = default; UseTLSDuringDestructionRunner(const UseTLSDuringDestructionRunner&) = delete; UseTLSDuringDestructionRunner& operator=( const UseTLSDuringDestructionRunner&) = delete; // The order in which pthread_key destructors are called is not well defined. // Hopefully, by creating 10 both before and after initializing TLS on the // thread, at least 1 will be called after TLS destruction. void Run() { ASSERT_FALSE(internal::ThreadLocalStorageTestInternal::HasBeenDestroyed()); // Create 10 pthread keys before initializing TLS on the thread. size_t slot_index = 0; for (; slot_index < 10; ++slot_index) { CreateTlsKeyWithDestructor(slot_index); } // Initialize the Chrome TLS system. It's possible that base::Thread has // already initialized Chrome TLS, but we don't rely on that. slot_.Set(reinterpret_cast(kDummyValue)); // Create 10 pthread keys after initializing TLS on the thread. for (; slot_index < kKeyCount; ++slot_index) { CreateTlsKeyWithDestructor(slot_index); } } bool teardown_works_correctly() { return teardown_works_correctly_; } private: struct TLSState { pthread_key_t key; raw_ptr teardown_works_correctly; }; // The POSIX TLS destruction API takes as input a single C-function, which is // called with the current |value| of a (key, value) pair. We need this // function to do two things: set the |value| to nullptr, which requires // knowing the associated |key|, and update the |teardown_works_correctly_| // state. // // To accomplish this, we set the value to an instance of TLSState, which // contains |key| as well as a pointer to |teardown_works_correctly|. static void ThreadLocalDestructor(void* value) { TLSState* state = static_cast(value); int result = pthread_setspecific(state->key, nullptr); ASSERT_EQ(result, 0); // If this path is hit, then the thread local destructor was called after // the Chrome-TLS destructor and the internal state was updated correctly. // No further checks are necessary. if (internal::ThreadLocalStorageTestInternal::HasBeenDestroyed()) { *(state->teardown_works_correctly) = true; return; } // If this path is hit, then the thread local destructor was called before // the Chrome-TLS destructor is hit. The ThreadLocalStorage::Slot should // still function correctly. ASSERT_EQ(reinterpret_cast(slot_.Get()), kDummyValue); } void CreateTlsKeyWithDestructor(size_t index) { ASSERT_LT(index, kKeyCount); tls_states_[index].teardown_works_correctly = &teardown_works_correctly_; int result = pthread_key_create( &(tls_states_[index].key), UseTLSDuringDestructionRunner::ThreadLocalDestructor); ASSERT_EQ(result, 0); result = pthread_setspecific(tls_states_[index].key, &tls_states_[index]); ASSERT_EQ(result, 0); } static base::ThreadLocalStorage::Slot slot_; bool teardown_works_correctly_ = false; TLSState tls_states_[kKeyCount]; }; base::ThreadLocalStorage::Slot UseTLSDuringDestructionRunner::slot_; void* UseTLSTestThreadRun(void* input) { UseTLSDuringDestructionRunner* runner = static_cast(input); runner->Run(); return nullptr; } #endif // BUILDFLAG(IS_POSIX) class TlsDestructionOrderRunner : public DelegateSimpleThread::Delegate { public: // The runner creates |n_slots| static slots that will be destroyed at // thread exit, with |spacing| empty slots between them. This allows us to // test that the destruction order is correct regardless of the actual slot // indices in the global array. TlsDestructionOrderRunner(int n_slots, int spacing) : n_slots_(n_slots), spacing_(spacing) {} void Run() override { destructor_calls.clear(); for (int slot = 1; slot < n_slots_ + 1; ++slot) { for (int i = 0; i < spacing_; ++i) { ThreadLocalStorage::Slot empty_slot(nullptr); } NewStaticTLSSlot(slot); } } static std::vector destructor_calls; private: ThreadLocalStorage::Slot& NewStaticTLSSlot(int n) { NoDestructor slot( &TlsDestructionOrderRunner::Destructor); slot->Set(reinterpret_cast(n)); return *slot; } static void Destructor(void* value) { int n = reinterpret_cast(value); destructor_calls.push_back(n); } int n_slots_; int spacing_; }; std::vector TlsDestructionOrderRunner::destructor_calls; class CreateDuringDestructionRunner : public DelegateSimpleThread::Delegate { public: void Run() override { second_destructor_called = false; NoDestructor slot( &CreateDuringDestructionRunner::FirstDestructor); slot->Set(reinterpret_cast(123)); } static bool second_destructor_called; private: // The first destructor allocates another TLS slot, which should also be // destroyed eventually. static void FirstDestructor(void*) { NoDestructor slot( &CreateDuringDestructionRunner::SecondDestructor); slot->Set(reinterpret_cast(234)); } static void SecondDestructor(void*) { second_destructor_called = true; } }; bool CreateDuringDestructionRunner::second_destructor_called = false; } // namespace TEST(ThreadLocalStorageTest, Basics) { ThreadLocalStorage::Slot slot; slot.Set(reinterpret_cast(123)); int value = reinterpret_cast(slot.Get()); EXPECT_EQ(value, 123); } #if defined(THREAD_SANITIZER) // Do not run the test under ThreadSanitizer. Because this test iterates its // own TSD destructor for the maximum possible number of times, TSan can't jump // in after the last destructor invocation, therefore the destructor remains // unsynchronized with the following users of the same TSD slot. This results // in race reports between the destructor and functions in other tests. #define MAYBE_TLSDestructors DISABLED_TLSDestructors #else #define MAYBE_TLSDestructors TLSDestructors #endif TEST(ThreadLocalStorageTest, MAYBE_TLSDestructors) { // Create a TLS index with a destructor. Create a set of // threads that set the TLS, while the destructor cleans it up. // After the threads finish, verify that the value is cleaned up. const int kNumThreads = 5; int values[kNumThreads]; ThreadLocalStorageRunner* thread_delegates[kNumThreads]; DelegateSimpleThread* threads[kNumThreads]; // Spawn the threads. for (int index = 0; index < kNumThreads; index++) { values[index] = kInitialTlsValue; thread_delegates[index] = new ThreadLocalStorageRunner(&values[index]); threads[index] = new DelegateSimpleThread(thread_delegates[index], "tls thread"); threads[index]->Start(); } // Wait for the threads to finish. for (int index = 0; index < kNumThreads; index++) { threads[index]->Join(); delete threads[index]; delete thread_delegates[index]; // Verify that the destructor was called and that we reset. EXPECT_EQ(values[index], kFinalTlsValue); } } TEST(ThreadLocalStorageTest, TLSReclaim) { // Creates and destroys many TLS slots and ensures they all zero-inited. for (int i = 0; i < 1000; ++i) { ThreadLocalStorage::Slot slot(nullptr); EXPECT_EQ(nullptr, slot.Get()); slot.Set(reinterpret_cast(0xBAADF00D)); EXPECT_EQ(reinterpret_cast(0xBAADF00D), slot.Get()); } } #if BUILDFLAG(IS_POSIX) // Unlike POSIX, Windows does not iterate through the OS TLS to cleanup any // values there. Instead a per-module thread destruction function is called. // However, it is not possible to perform a check after this point (as the code // is detached from the thread), so this check remains POSIX only. TEST(ThreadLocalStorageTest, UseTLSDuringDestruction) { UseTLSDuringDestructionRunner runner; pthread_t thread; int result = pthread_create(&thread, nullptr, UseTLSTestThreadRun, &runner); ASSERT_EQ(result, 0); result = pthread_join(thread, nullptr); ASSERT_EQ(result, 0); EXPECT_TRUE(runner.teardown_works_correctly()); } #endif // BUILDFLAG(IS_POSIX) // Test that TLS slots are destroyed in the reverse order: the one that was // created first is destroyed last. TEST(ThreadLocalStorageTest, DestructionOrder) { const size_t kNSlots = 5; const size_t kSpacing = 100; // The total number of slots is 256, so creating 5 slots with 100 space // between them will place them in different parts of the slot array. // This test checks that their destruction order depends only on their // creation order and not on their index in the array. TlsDestructionOrderRunner runner(kNSlots, kSpacing); DelegateSimpleThread thread(&runner, "tls thread"); thread.Start(); thread.Join(); ASSERT_EQ(kNSlots, TlsDestructionOrderRunner::destructor_calls.size()); for (int call = 0, slot = kNSlots; slot > 0; --slot, ++call) { EXPECT_EQ(slot, TlsDestructionOrderRunner::destructor_calls[call]); } } TEST(ThreadLocalStorageTest, CreateDuringDestruction) { CreateDuringDestructionRunner runner; DelegateSimpleThread thread(&runner, "tls thread"); thread.Start(); thread.Join(); ASSERT_TRUE(CreateDuringDestructionRunner::second_destructor_called); } } // namespace base