| // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| // 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" | |
| #if defined(OS_WIN) | |
| #include <windows.h> | |
| #include <process.h> | |
| #endif | |
| #include "base/macros.h" | |
| #include "base/no_destructor.h" | |
| #include "base/threading/simple_thread.h" | |
| #include "build/build_config.h" | |
| #include "starboard/types.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| #if defined(OS_WIN) | |
| // Ignore warnings about ptr->int conversions that we use when | |
| // storing ints into ThreadLocalStorage. | |
| #pragma warning(disable : 4311 4312) | |
| #endif | |
| namespace base { | |
| #if defined(OS_POSIX) | |
| namespace internal { | |
| // This class is friended by ThreadLocalStorage. | |
| class ThreadLocalStorageTestInternal { | |
| public: | |
| static bool HasBeenDestroyed() { | |
| return ThreadLocalStorage::HasBeenDestroyed(); | |
| } | |
| }; | |
| } // namespace internal | |
| #endif // defined(OS_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<ThreadLocalStorage::Slot> slot( | |
| &ThreadLocalStorageCleanup); | |
| return *slot; | |
| } | |
| class ThreadLocalStorageRunner : public DelegateSimpleThread::Delegate { | |
| public: | |
| explicit ThreadLocalStorageRunner(int* tls_value_ptr) | |
| : tls_value_ptr_(tls_value_ptr) {} | |
| ~ThreadLocalStorageRunner() override = default; | |
| void Run() override { | |
| *tls_value_ptr_ = kInitialTlsValue; | |
| TLSSlot().Set(tls_value_ptr_); | |
| int* ptr = static_cast<int*>(TLSSlot().Get()); | |
| EXPECT_EQ(ptr, tls_value_ptr_); | |
| EXPECT_EQ(*ptr, kInitialTlsValue); | |
| *tls_value_ptr_ = 0; | |
| ptr = static_cast<int*>(TLSSlot().Get()); | |
| EXPECT_EQ(ptr, tls_value_ptr_); | |
| EXPECT_EQ(*ptr, 0); | |
| *ptr = kFinalTlsValue + kNumberDestructorCallRepetitions; | |
| } | |
| private: | |
| int* tls_value_ptr_; | |
| DISALLOW_COPY_AND_ASSIGN(ThreadLocalStorageRunner); | |
| }; | |
| void ThreadLocalStorageCleanup(void *value) { | |
| int *ptr = reinterpret_cast<int*>(value); | |
| // Destructors should never be called with a NULL. | |
| ASSERT_NE(reinterpret_cast<int*>(NULL), 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 defined(OS_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; | |
| // 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<void*>(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; | |
| bool* 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<TLSState*>(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<intptr_t>(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]; | |
| DISALLOW_COPY_AND_ASSIGN(UseTLSDuringDestructionRunner); | |
| }; | |
| base::ThreadLocalStorage::Slot UseTLSDuringDestructionRunner::slot_; | |
| void* UseTLSTestThreadRun(void* input) { | |
| UseTLSDuringDestructionRunner* runner = | |
| static_cast<UseTLSDuringDestructionRunner*>(input); | |
| runner->Run(); | |
| return nullptr; | |
| } | |
| #endif // defined(OS_POSIX) | |
| } // namespace | |
| TEST(ThreadLocalStorageTest, Basics) { | |
| ThreadLocalStorage::Slot slot; | |
| slot.Set(reinterpret_cast<void*>(123)); | |
| int value = reinterpret_cast<intptr_t>(slot.Get()); | |
| EXPECT_EQ(value, 123); | |
| } | |
| #if defined(THREAD_SANITIZER) || \ | |
| (defined(OS_WIN) && defined(ARCH_CPU_X86_64) && !defined(NDEBUG)) | |
| // 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. | |
| // | |
| // It is disabled on Win x64 with incremental linking (i.e. "Debug") pending | |
| // resolution of http://crbug.com/251251. | |
| #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); | |
| } | |
| } | |
| // MSVC doesn't allow casting 32bit address to pointer. | |
| #if !SB_IS(COMPILER_MSVC) | |
| 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<void*>(0xBAADF00D)); | |
| EXPECT_EQ(reinterpret_cast<void*>(0xBAADF00D), slot.Get()); | |
| } | |
| } | |
| #endif | |
| #if defined(OS_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 // defined(OS_POSIX) | |
| } // namespace base |