| // 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.h" | |
| #include <optional> | |
| #include "base/check_op.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/synchronization/waitable_event.h" | |
| #include "base/test/bind.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 { | |
| // A simple helper which sets the given boolean to true on destruction. | |
| class SetTrueOnDestruction { | |
| public: | |
| explicit SetTrueOnDestruction(bool* was_destroyed) | |
| : was_destroyed_(was_destroyed) { | |
| CHECK_NE(was_destroyed, nullptr); | |
| } | |
| SetTrueOnDestruction(const SetTrueOnDestruction&) = delete; | |
| SetTrueOnDestruction& operator=(const SetTrueOnDestruction&) = delete; | |
| ~SetTrueOnDestruction() { | |
| EXPECT_FALSE(*was_destroyed_); | |
| *was_destroyed_ = true; | |
| } | |
| private: | |
| const raw_ptr<bool> was_destroyed_; | |
| }; | |
| } // 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) { | |
| std::optional<ThreadLocalOwnedPointer<SetTrueOnDestruction>> | |
| tls_owned_pointer(std::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) { | |
| GTEST_FLAG_SET(death_test_style, "threadsafe"); | |
| std::optional<ThreadLocalOwnedPointer<int>> tls_owned_pointer(std::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); | |
| } | |
| } // namespace base |