blob: 4bde84d2a7dd07e4dc3bc6ec58ddea2f3afcccad [file] [log] [blame]
// 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