| // 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.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/optional.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/gtest_util.h" |
| #include "base/threading/simple_thread.h" |
| #include "base/threading/thread.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace base { |
| |
| namespace { |
| |
| class ThreadLocalTesterBase : public DelegateSimpleThreadPool::Delegate { |
| public: |
| typedef ThreadLocalPointer<char> TLPType; |
| |
| ThreadLocalTesterBase(TLPType* tlp, WaitableEvent* done) |
| : tlp_(tlp), done_(done) {} |
| ~ThreadLocalTesterBase() override = default; |
| |
| protected: |
| TLPType* tlp_; |
| WaitableEvent* done_; |
| }; |
| |
| class SetThreadLocal : public ThreadLocalTesterBase { |
| public: |
| SetThreadLocal(TLPType* tlp, WaitableEvent* done) |
| : ThreadLocalTesterBase(tlp, done), val_(nullptr) {} |
| ~SetThreadLocal() override = default; |
| |
| void set_value(char* val) { val_ = val; } |
| |
| void Run() override { |
| DCHECK(!done_->IsSignaled()); |
| tlp_->Set(val_); |
| done_->Signal(); |
| } |
| |
| private: |
| char* val_; |
| }; |
| |
| class GetThreadLocal : public ThreadLocalTesterBase { |
| public: |
| GetThreadLocal(TLPType* tlp, WaitableEvent* done) |
| : ThreadLocalTesterBase(tlp, done), ptr_(nullptr) {} |
| ~GetThreadLocal() override = default; |
| |
| void set_ptr(char** ptr) { ptr_ = ptr; } |
| |
| void Run() override { |
| DCHECK(!done_->IsSignaled()); |
| *ptr_ = tlp_->Get(); |
| done_->Signal(); |
| } |
| |
| private: |
| char** ptr_; |
| }; |
| |
| } // namespace |
| |
| // In this test, we start 2 threads which will access a ThreadLocalPointer. We |
| // make sure the default is NULL, and the pointers are unique to the threads. |
| TEST(ThreadLocalTest, Pointer) { |
| DelegateSimpleThreadPool tp1("ThreadLocalTest tp1", 1); |
| DelegateSimpleThreadPool tp2("ThreadLocalTest tp1", 1); |
| tp1.Start(); |
| tp2.Start(); |
| |
| ThreadLocalPointer<char> tlp; |
| |
| static char* const kBogusPointer = reinterpret_cast<char*>(0x1234); |
| |
| char* tls_val; |
| WaitableEvent done(WaitableEvent::ResetPolicy::MANUAL, |
| WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| GetThreadLocal getter(&tlp, &done); |
| getter.set_ptr(&tls_val); |
| |
| // Check that both threads defaulted to NULL. |
| tls_val = kBogusPointer; |
| done.Reset(); |
| tp1.AddWork(&getter); |
| done.Wait(); |
| EXPECT_EQ(static_cast<char*>(nullptr), tls_val); |
| |
| tls_val = kBogusPointer; |
| done.Reset(); |
| tp2.AddWork(&getter); |
| done.Wait(); |
| EXPECT_EQ(static_cast<char*>(nullptr), tls_val); |
| |
| SetThreadLocal setter(&tlp, &done); |
| setter.set_value(kBogusPointer); |
| |
| // Have thread 1 set their pointer value to kBogusPointer. |
| done.Reset(); |
| tp1.AddWork(&setter); |
| done.Wait(); |
| |
| tls_val = nullptr; |
| done.Reset(); |
| tp1.AddWork(&getter); |
| done.Wait(); |
| EXPECT_EQ(kBogusPointer, tls_val); |
| |
| // Make sure thread 2 is still NULL |
| tls_val = kBogusPointer; |
| done.Reset(); |
| tp2.AddWork(&getter); |
| done.Wait(); |
| EXPECT_EQ(static_cast<char*>(nullptr), tls_val); |
| |
| // Set thread 2 to kBogusPointer + 1. |
| setter.set_value(kBogusPointer + 1); |
| |
| done.Reset(); |
| tp2.AddWork(&setter); |
| done.Wait(); |
| |
| tls_val = nullptr; |
| done.Reset(); |
| tp2.AddWork(&getter); |
| done.Wait(); |
| EXPECT_EQ(kBogusPointer + 1, tls_val); |
| |
| // Make sure thread 1 is still kBogusPointer. |
| tls_val = nullptr; |
| done.Reset(); |
| tp1.AddWork(&getter); |
| done.Wait(); |
| EXPECT_EQ(kBogusPointer, tls_val); |
| |
| tp1.JoinAll(); |
| tp2.JoinAll(); |
| } |
| |
| namespace { |
| |
| // A simple helper which sets the given boolean to true on destruction. |
| class SetTrueOnDestruction { |
| public: |
| SetTrueOnDestruction(bool* was_destroyed) : was_destroyed_(was_destroyed) { |
| CHECK(was_destroyed != nullptr); |
| } |
| ~SetTrueOnDestruction() { |
| EXPECT_FALSE(*was_destroyed_); |
| *was_destroyed_ = true; |
| } |
| |
| private: |
| bool* const was_destroyed_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SetTrueOnDestruction); |
| }; |
| |
| } // namespace |
| |
| TEST(ThreadLocalTest, ThreadLocalOwnedPointerBasic) { |
| ThreadLocalOwnedPointer<SetTrueOnDestruction> tls_owned_pointer; |
| EXPECT_FALSE(tls_owned_pointer.Get()); |
| |
| bool was_destroyed1 = false; |
| tls_owned_pointer.Set( |
| std::make_unique<SetTrueOnDestruction>(&was_destroyed1)); |
| EXPECT_FALSE(was_destroyed1); |
| EXPECT_TRUE(tls_owned_pointer.Get()); |
| |
| bool was_destroyed2 = false; |
| tls_owned_pointer.Set( |
| std::make_unique<SetTrueOnDestruction>(&was_destroyed2)); |
| EXPECT_TRUE(was_destroyed1); |
| EXPECT_FALSE(was_destroyed2); |
| EXPECT_TRUE(tls_owned_pointer.Get()); |
| |
| tls_owned_pointer.Set(nullptr); |
| EXPECT_TRUE(was_destroyed1); |
| EXPECT_TRUE(was_destroyed2); |
| EXPECT_FALSE(tls_owned_pointer.Get()); |
| } |
| |
| TEST(ThreadLocalTest, ThreadLocalOwnedPointerFreedOnThreadExit) { |
| bool tls_was_destroyed = false; |
| ThreadLocalOwnedPointer<SetTrueOnDestruction> tls_owned_pointer; |
| |
| Thread thread("TestThread"); |
| thread.Start(); |
| |
| WaitableEvent tls_set; |
| |
| thread.task_runner()->PostTask( |
| FROM_HERE, BindLambdaForTesting([&]() { |
| tls_owned_pointer.Set( |
| std::make_unique<SetTrueOnDestruction>(&tls_was_destroyed)); |
| tls_set.Signal(); |
| })); |
| |
| tls_set.Wait(); |
| EXPECT_FALSE(tls_was_destroyed); |
| |
| thread.Stop(); |
| EXPECT_TRUE(tls_was_destroyed); |
| } |
| |
| TEST(ThreadLocalTest, ThreadLocalOwnedPointerCleansUpMainThreadOnDestruction) { |
| base::Optional<ThreadLocalOwnedPointer<SetTrueOnDestruction>> |
| tls_owned_pointer(base::in_place); |
| bool tls_was_destroyed_other = false; |
| |
| Thread thread("TestThread"); |
| thread.Start(); |
| |
| WaitableEvent tls_set; |
| |
| thread.task_runner()->PostTask( |
| FROM_HERE, BindLambdaForTesting([&]() { |
| tls_owned_pointer->Set( |
| std::make_unique<SetTrueOnDestruction>(&tls_was_destroyed_other)); |
| tls_set.Signal(); |
| })); |
| |
| tls_set.Wait(); |
| |
| bool tls_was_destroyed_main = false; |
| tls_owned_pointer->Set( |
| std::make_unique<SetTrueOnDestruction>(&tls_was_destroyed_main)); |
| EXPECT_FALSE(tls_was_destroyed_other); |
| EXPECT_FALSE(tls_was_destroyed_main); |
| |
| // Stopping the thread relinquishes its TLS (as in |
| // ThreadLocalOwnedPointerFreedOnThreadExit). |
| thread.Stop(); |
| EXPECT_TRUE(tls_was_destroyed_other); |
| EXPECT_FALSE(tls_was_destroyed_main); |
| |
| // Deleting the ThreadLocalOwnedPointer instance on the main thread is allowed |
| // iff that's the only thread with remaining storage (ref. disallowed use case |
| // in ThreadLocalOwnedPointerDeathIfDestroyedWithActiveThread below). In that |
| // case, the storage on the main thread is freed before releasing the TLS |
| // slot. |
| tls_owned_pointer.reset(); |
| EXPECT_TRUE(tls_was_destroyed_main); |
| } |
| |
| TEST(ThreadLocalTest, ThreadLocalOwnedPointerDeathIfDestroyedWithActiveThread) { |
| testing::FLAGS_gtest_death_test_style = "threadsafe"; |
| |
| base::Optional<ThreadLocalOwnedPointer<int>> tls_owned_pointer( |
| base::in_place); |
| |
| Thread thread("TestThread"); |
| thread.Start(); |
| |
| WaitableEvent tls_set; |
| |
| thread.task_runner()->PostTask( |
| FROM_HERE, BindLambdaForTesting([&]() { |
| tls_owned_pointer->Set(std::make_unique<int>(1)); |
| tls_set.Signal(); |
| })); |
| |
| tls_set.Wait(); |
| |
| EXPECT_DCHECK_DEATH({ tls_owned_pointer.reset(); }); |
| } |
| |
| TEST(ThreadLocalTest, ThreadLocalOwnedPointerMultiThreadedAndStaticStorage) { |
| constexpr int kNumThreads = 16; |
| |
| static ThreadLocalOwnedPointer<SetTrueOnDestruction> tls_owned_pointer; |
| |
| std::array<bool, kNumThreads> were_destroyed{}; |
| |
| std::array<std::unique_ptr<Thread>, kNumThreads> threads; |
| |
| for (auto& thread : threads) { |
| thread = std::make_unique<Thread>("TestThread"); |
| thread->Start(); |
| } |
| |
| for (const auto& thread : threads) { |
| // Waiting is unnecessary but enhances the likelihood of data races in the |
| // next steps. |
| thread->WaitUntilThreadStarted(); |
| } |
| |
| for (const bool was_destroyed : were_destroyed) { |
| EXPECT_FALSE(was_destroyed); |
| } |
| |
| for (int i = 0; i < kNumThreads; ++i) { |
| threads[i]->task_runner()->PostTask( |
| FROM_HERE, |
| BindOnce( |
| [](bool* was_destroyed) { |
| tls_owned_pointer.Set( |
| std::make_unique<SetTrueOnDestruction>(was_destroyed)); |
| }, |
| &were_destroyed[i])); |
| } |
| |
| static bool main_thread_was_destroyed = false; |
| // Even when the test is run multiple times in the same process: TLS should |
| // never be destroyed until static uninitialization. |
| EXPECT_FALSE(main_thread_was_destroyed); |
| |
| tls_owned_pointer.Set( |
| std::make_unique<SetTrueOnDestruction>(&main_thread_was_destroyed)); |
| |
| for (const auto& thread : threads) { |
| thread->Stop(); |
| } |
| |
| for (const bool was_destroyed : were_destroyed) { |
| EXPECT_TRUE(was_destroyed); |
| } |
| |
| // The main thread's TLS still wasn't destroyed (let the test unfold naturally |
| // through static uninitialization). |
| EXPECT_FALSE(main_thread_was_destroyed); |
| } |
| |
| TEST(ThreadLocalTest, Boolean) { |
| { |
| ThreadLocalBoolean tlb; |
| EXPECT_FALSE(tlb.Get()); |
| |
| tlb.Set(false); |
| EXPECT_FALSE(tlb.Get()); |
| |
| tlb.Set(true); |
| EXPECT_TRUE(tlb.Get()); |
| } |
| |
| // Our slot should have been freed, we're all reset. |
| { |
| ThreadLocalBoolean tlb; |
| EXPECT_FALSE(tlb.Get()); |
| } |
| } |
| |
| } // namespace base |