blob: ccbe08a7da7ea99b4b6f3a7eca7bd83881b34b80 [file] [log] [blame]
// Copyright 2022 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/allocator/dispatcher/tls.h"
#if USE_LOCAL_TLS_EMULATION()
#include <algorithm>
#include <array>
#include <cstddef>
#include <functional>
#include <mutex>
#include <thread>
#include <unordered_set>
#include <utility>
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::InSequence;
using ::testing::NiceMock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::ReturnNull;
namespace base::allocator::dispatcher {
namespace {
struct DataToStore {
int data_int = 0;
float data_float = 0.0;
size_t data_size_t = 0;
double data_double = 0.0;
};
struct AllocatorMockBase {
AllocatorMockBase() {
ON_CALL(*this, AllocateMemory(_)).WillByDefault([](size_t size_in_bytes) {
return malloc(size_in_bytes);
});
ON_CALL(*this, FreeMemoryForTesting(_, _))
.WillByDefault([](void* pointer_to_allocated, size_t size_in_bytes) {
free(pointer_to_allocated);
return true;
});
}
MOCK_METHOD(void*, AllocateMemory, (size_t size_in_bytes), ());
MOCK_METHOD(bool,
FreeMemoryForTesting,
(void* pointer_to_allocated, size_t size_in_bytes),
());
};
struct TLSSystemMockBase {
TLSSystemMockBase() {
ON_CALL(*this, Setup(_)).WillByDefault(Return(true));
ON_CALL(*this, TearDownForTesting()).WillByDefault(Return(true));
ON_CALL(*this, SetThreadSpecificData(_)).WillByDefault(Return(true));
}
MOCK_METHOD(
bool,
Setup,
(internal::OnThreadTerminationFunction thread_termination_function),
());
MOCK_METHOD(bool, TearDownForTesting, (), ());
MOCK_METHOD(void*, GetThreadSpecificData, (), ());
MOCK_METHOD(bool, SetThreadSpecificData, (void* data), ());
};
using AllocatorMock = NiceMock<AllocatorMockBase>;
using TLSSystemMock = NiceMock<TLSSystemMockBase>;
template <typename T, typename Allocator, typename TLSSystem>
ThreadLocalStorage<T,
std::reference_wrapper<Allocator>,
std::reference_wrapper<TLSSystem>,
0,
true>
CreateThreadLocalStorage(Allocator& allocator, TLSSystem& tlsSystem) {
return {std::ref(allocator), std::ref(tlsSystem)};
}
template <typename T>
ThreadLocalStorage<T,
internal::DefaultAllocator,
internal::DefaultTLSSystem,
0,
true>
CreateThreadLocalStorage() {
return {};
}
} // namespace
struct BaseThreadLocalStorageTest : public ::testing::Test {};
TEST_F(BaseThreadLocalStorageTest,
VerifyDataIsIndependentBetweenDifferentSUTs) {
auto sut_1 = CreateThreadLocalStorage<DataToStore>();
auto sut_2 = CreateThreadLocalStorage<DataToStore>();
EXPECT_NE(sut_1.GetThreadLocalData(), sut_2.GetThreadLocalData());
}
TEST_F(BaseThreadLocalStorageTest, VerifyDistinctEntriesForEachThread) {
auto sut = CreateThreadLocalStorage<DataToStore>();
using TLSType = decltype(sut);
std::array<std::thread, 2 * TLSType::ItemsPerChunk> threads;
std::mutex thread_worker_mutex;
std::condition_variable thread_counter_cv;
std::atomic_uint32_t thread_counter{0};
std::unordered_set<void*> stored_object_addresses;
std::mutex threads_can_finish_mutex;
std::condition_variable threads_can_finish_cv;
std::atomic_bool threads_can_finish{false};
for (auto& t : threads) {
t = std::thread{[&] {
{
std::lock_guard<std::mutex> lock(thread_worker_mutex);
stored_object_addresses.insert(sut.GetThreadLocalData());
++thread_counter;
thread_counter_cv.notify_one();
}
{
std::unique_lock<std::mutex> lock(threads_can_finish_mutex);
threads_can_finish_cv.wait(lock,
[&] { return threads_can_finish.load(); });
}
}};
}
{
std::unique_lock<std::mutex> lock(thread_worker_mutex);
thread_counter_cv.wait(
lock, [&] { return thread_counter.load() == threads.size(); });
}
{
std::unique_lock<std::mutex> lock(threads_can_finish_mutex);
threads_can_finish = true;
threads_can_finish_cv.notify_all();
}
for (auto& t : threads) {
t.join();
}
EXPECT_EQ(stored_object_addresses.size(), threads.size());
}
TEST_F(BaseThreadLocalStorageTest, VerifyEntriesAreReusedForNewThreads) {
auto sut = CreateThreadLocalStorage<DataToStore>();
using TLSType = decltype(sut);
std::unordered_set<void*> stored_object_addresses;
for (size_t thread_count = 0; thread_count < (2 * TLSType::ItemsPerChunk);
++thread_count) {
auto thread = std::thread{
[&] { stored_object_addresses.insert(sut.GetThreadLocalData()); }};
thread.join();
}
EXPECT_EQ(stored_object_addresses.size(), 1ul);
}
TEST_F(BaseThreadLocalStorageTest, VerifyDataIsSameWithinEachThread) {
auto sut = CreateThreadLocalStorage<DataToStore>();
using TLSType = decltype(sut);
std::array<std::thread, 2 * TLSType::ItemsPerChunk> threads;
for (auto& t : threads) {
t = std::thread{[&] {
EXPECT_EQ(sut.GetThreadLocalData(), sut.GetThreadLocalData());
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Check once again to verify the data doesn't change in the course of a
// thread's lifetime.
EXPECT_EQ(sut.GetThreadLocalData(), sut.GetThreadLocalData());
}};
}
for (auto& t : threads) {
t.join();
}
}
TEST_F(BaseThreadLocalStorageTest, VerifySetupTeardownSequence) {
AllocatorMock allocator_mock;
TLSSystemMock tlsSystem_mock;
InSequence execution_sequence;
EXPECT_CALL(allocator_mock, AllocateMemory(_))
.WillOnce([](size_t size_in_bytes) { return malloc(size_in_bytes); });
EXPECT_CALL(tlsSystem_mock, Setup(NotNull())).WillOnce(Return(true));
EXPECT_CALL(tlsSystem_mock, TearDownForTesting()).WillOnce(Return(true));
EXPECT_CALL(allocator_mock, FreeMemoryForTesting(_, _))
.WillOnce([](void* pointer_to_allocated, size_t size_in_bytes) {
free(pointer_to_allocated);
return true;
});
auto sut =
CreateThreadLocalStorage<DataToStore>(allocator_mock, tlsSystem_mock);
}
TEST_F(BaseThreadLocalStorageTest, VerifyAllocatorIsUsed) {
AllocatorMock allocator_mock;
TLSSystemMock tlsSystem_mock;
EXPECT_CALL(allocator_mock, AllocateMemory(_))
.WillOnce([](size_t size_in_bytes) { return malloc(size_in_bytes); });
EXPECT_CALL(allocator_mock, FreeMemoryForTesting(_, _))
.WillOnce([](void* pointer_to_allocated, size_t size_in_bytes) {
free(pointer_to_allocated);
return true;
});
auto sut =
CreateThreadLocalStorage<DataToStore>(allocator_mock, tlsSystem_mock);
}
TEST_F(BaseThreadLocalStorageTest, VerifyAllocatorIsUsedForMultipleChunks) {
AllocatorMock allocator_mock;
TLSSystemMock tlsSystem_mock;
constexpr auto number_of_chunks = 3;
EXPECT_CALL(allocator_mock, AllocateMemory(_))
.Times(number_of_chunks)
.WillRepeatedly(
[](size_t size_in_bytes) { return malloc(size_in_bytes); });
EXPECT_CALL(allocator_mock, FreeMemoryForTesting(_, _))
.Times(number_of_chunks)
.WillRepeatedly([](void* pointer_to_allocated, size_t size_in_bytes) {
free(pointer_to_allocated);
return true;
});
auto sut =
CreateThreadLocalStorage<DataToStore>(allocator_mock, tlsSystem_mock);
std::array<std::thread,
(number_of_chunks - 1) * decltype(sut)::ItemsPerChunk + 1>
threads;
std::mutex thread_worker_mutex;
std::condition_variable thread_counter_cv;
std::atomic_uint32_t thread_counter{0};
std::unordered_set<void*> stored_object_addresses;
std::mutex threads_can_finish_mutex;
std::condition_variable threads_can_finish_cv;
std::atomic_bool threads_can_finish{false};
for (auto& t : threads) {
t = std::thread{[&] {
sut.GetThreadLocalData();
{
std::lock_guard<std::mutex> lock(thread_worker_mutex);
++thread_counter;
thread_counter_cv.notify_one();
}
{
std::unique_lock<std::mutex> lock(threads_can_finish_mutex);
threads_can_finish_cv.wait(lock,
[&] { return threads_can_finish.load(); });
}
}};
}
{
std::unique_lock<std::mutex> lock(thread_worker_mutex);
thread_counter_cv.wait(
lock, [&] { return thread_counter.load() == threads.size(); });
}
{
std::unique_lock<std::mutex> lock(threads_can_finish_mutex);
threads_can_finish = true;
threads_can_finish_cv.notify_all();
}
for (auto& t : threads) {
t.join();
}
}
TEST_F(BaseThreadLocalStorageTest, VerifyTLSSystemIsUsed) {
AllocatorMock allocator_mock;
TLSSystemMock tlsSystem_mock;
InSequence execution_sequence;
EXPECT_CALL(tlsSystem_mock, Setup(NotNull())).WillOnce(Return(true));
EXPECT_CALL(tlsSystem_mock, GetThreadSpecificData())
.WillOnce(Return(nullptr));
EXPECT_CALL(tlsSystem_mock, SetThreadSpecificData(NotNull()));
EXPECT_CALL(tlsSystem_mock, TearDownForTesting())
.Times(1)
.WillOnce(Return(true));
auto sut =
CreateThreadLocalStorage<DataToStore>(allocator_mock, tlsSystem_mock);
sut.GetThreadLocalData();
}
#if defined(GTEST_HAS_DEATH_TEST)
struct BaseThreadLocalStorageDeathTest : public ::testing::Test {};
TEST_F(BaseThreadLocalStorageDeathTest, VerifyDeathIfAllocationFails) {
auto f = [] {
AllocatorMock allocator_mock;
TLSSystemMock tlsSystem_mock;
// Setup all expectations here. If we're setting them up in the parent
// process, they will fail because the parent doesn't execute any test.
EXPECT_CALL(allocator_mock, AllocateMemory(_)).WillOnce(ReturnNull());
CreateThreadLocalStorage<DataToStore>(allocator_mock, tlsSystem_mock);
};
EXPECT_DEATH(f(), "");
}
TEST_F(BaseThreadLocalStorageDeathTest, VerifyDeathIfFreeFails) {
auto f = [] {
AllocatorMock allocator_mock;
TLSSystemMock tlsSystem_mock;
// Setup all expectations here. If we're setting them up in the parent
// process, they will fail because the parent doesn't execute any test.
EXPECT_CALL(allocator_mock, FreeMemoryForTesting(_, _))
.WillOnce([](void* allocated_memory, size_t size_in_bytes) {
free(allocated_memory);
return false;
});
CreateThreadLocalStorage<DataToStore>(allocator_mock, tlsSystem_mock);
};
EXPECT_DEATH(f(), "");
}
TEST_F(BaseThreadLocalStorageDeathTest, VerifyDeathIfTLSSetupFails) {
auto f = [] {
AllocatorMock allocator_mock;
TLSSystemMock tlsSystem_mock;
// Setup all expectations here. If we're setting them up in the parent
// process, they will fail because the parent doesn't execute any test.
EXPECT_CALL(tlsSystem_mock, Setup(_)).WillOnce(Return(false));
EXPECT_CALL(tlsSystem_mock, GetThreadSpecificData()).Times(0);
EXPECT_CALL(tlsSystem_mock, SetThreadSpecificData(_)).Times(0);
EXPECT_CALL(tlsSystem_mock, TearDownForTesting()).Times(0);
CreateThreadLocalStorage<DataToStore>(allocator_mock, tlsSystem_mock);
};
EXPECT_DEATH(f(), "");
}
TEST_F(BaseThreadLocalStorageDeathTest, VerifyDeathIfStoringTLSDataFails) {
auto f = [] {
AllocatorMock allocator_mock;
TLSSystemMock tlsSystem_mock;
// Setup all expectations here. If we're setting them up in the parent
// process, they will fail because the parent doesn't execute any test.
EXPECT_CALL(tlsSystem_mock, SetThreadSpecificData(_))
.Times(1)
.WillOnce(Return(false));
EXPECT_CALL(tlsSystem_mock, TearDownForTesting()).Times(0);
CreateThreadLocalStorage<DataToStore>(allocator_mock, tlsSystem_mock)
.GetThreadLocalData();
};
EXPECT_DEATH(f(), "");
}
TEST_F(BaseThreadLocalStorageDeathTest, VerifyDeathIfTLSTeardownFails) {
auto f = [] {
AllocatorMock allocator_mock;
TLSSystemMock tlsSystem_mock;
// Setup all expectations here. If we're setting them up in the parent
// process, they will fail because the parent doesn't execute any test.
EXPECT_CALL(tlsSystem_mock, Setup(_)).WillOnce(Return(true));
EXPECT_CALL(tlsSystem_mock, TearDownForTesting()).WillOnce(Return(false));
CreateThreadLocalStorage<DataToStore>(allocator_mock, tlsSystem_mock);
};
EXPECT_DEATH(f(), "");
}
#endif // GTEST_HAS_DEATH_TEST
struct BasePThreadTLSSystemTest : public ::testing::Test {
void SetUp() override { thread_termination_counter = 0; }
protected:
static void ThreadTerminationFunction(void*) { ++thread_termination_counter; }
static std::atomic<size_t> thread_termination_counter;
};
std::atomic<size_t> BasePThreadTLSSystemTest::thread_termination_counter{0};
TEST_F(BasePThreadTLSSystemTest, VerifySetupNTeardownSequence) {
internal::PThreadTLSSystem sut;
for (size_t idx = 0; idx < 5; ++idx) {
EXPECT_TRUE(sut.Setup(nullptr));
EXPECT_TRUE(sut.TearDownForTesting());
}
}
TEST_F(BasePThreadTLSSystemTest, VerifyThreadTerminationFunctionIsCalled) {
std::array<std::thread, 10> threads;
internal::PThreadTLSSystem sut;
sut.Setup(&ThreadTerminationFunction);
for (auto& t : threads) {
t = std::thread{[&] {
int x = 0;
ASSERT_TRUE(sut.SetThreadSpecificData(&x));
}};
}
for (auto& t : threads) {
t.join();
}
sut.TearDownForTesting();
EXPECT_EQ(threads.size(), thread_termination_counter);
}
TEST_F(BasePThreadTLSSystemTest, VerifyGetWithoutSetReturnsNull) {
internal::PThreadTLSSystem sut;
sut.Setup(nullptr);
EXPECT_EQ(nullptr, sut.GetThreadSpecificData());
sut.TearDownForTesting();
}
TEST_F(BasePThreadTLSSystemTest, VerifyGetAfterTeardownReturnsNull) {
internal::PThreadTLSSystem sut;
sut.Setup(nullptr);
sut.SetThreadSpecificData(this);
sut.TearDownForTesting();
EXPECT_EQ(sut.GetThreadSpecificData(), nullptr);
}
TEST_F(BasePThreadTLSSystemTest, VerifyGetAfterTeardownReturnsNullThreaded) {
std::array<std::thread, 50> threads;
std::mutex thread_worker_mutex;
std::condition_variable thread_counter_cv;
std::atomic_uint32_t thread_counter{0};
std::mutex threads_can_finish_mutex;
std::condition_variable threads_can_finish_cv;
std::atomic_bool threads_can_finish{false};
internal::PThreadTLSSystem sut;
ASSERT_TRUE(sut.Setup(nullptr));
for (auto& t : threads) {
t = std::thread{[&] {
// Set some thread specific data. At this stage retrieving the data must
// return the pointer that was originally set.
int x = 0;
ASSERT_TRUE(sut.SetThreadSpecificData(&x));
ASSERT_EQ(sut.GetThreadSpecificData(), &x);
// Notify the main thread that one more test thread has started.
{
std::lock_guard<std::mutex> lock(thread_worker_mutex);
++thread_counter;
thread_counter_cv.notify_one();
}
// Wait for the main thread to notify about teardown of the sut.
{
std::unique_lock<std::mutex> lock(threads_can_finish_mutex);
threads_can_finish_cv.wait(lock,
[&] { return threads_can_finish.load(); });
}
// After teardown, thread local data must be nullptr for all threads.
EXPECT_EQ(sut.GetThreadSpecificData(), nullptr);
}};
}
// Wait for notification from threads that they started and passed the initial
// check.
{
std::unique_lock<std::mutex> lock(thread_worker_mutex);
thread_counter_cv.wait(
lock, [&] { return thread_counter.load() == threads.size(); });
}
ASSERT_TRUE(sut.TearDownForTesting());
// Notify all threads that the subject under test was torn down and they can
// proceed.
{
std::unique_lock<std::mutex> lock(threads_can_finish_mutex);
threads_can_finish = true;
threads_can_finish_cv.notify_all();
}
for (auto& t : threads) {
t.join();
}
}
TEST_F(BasePThreadTLSSystemTest, VerifyGetSetSequence) {
std::array<std::thread, 50> threads;
internal::PThreadTLSSystem sut;
sut.Setup(nullptr);
for (auto& t : threads) {
t = std::thread{[&] {
int x = 0;
EXPECT_TRUE(sut.SetThreadSpecificData(&x));
EXPECT_EQ(&x, sut.GetThreadSpecificData());
}};
}
for (auto& t : threads) {
t.join();
}
sut.TearDownForTesting();
}
#if DCHECK_IS_ON()
TEST_F(BasePThreadTLSSystemTest, VerifyGetWithoutSetupReturnsNull) {
internal::PThreadTLSSystem sut;
EXPECT_EQ(sut.GetThreadSpecificData(), nullptr);
}
TEST_F(BasePThreadTLSSystemTest, VerifyStoreWithoutSetupFails) {
internal::PThreadTLSSystem sut;
EXPECT_FALSE(sut.SetThreadSpecificData(this));
}
#endif
#if defined(GTEST_HAS_DEATH_TEST) && DCHECK_IS_ON()
struct BasePThreadTLSSystemDeathTest : public ::testing::Test {};
TEST_F(BasePThreadTLSSystemDeathTest, VerifyDeathIfSetupTwice) {
internal::PThreadTLSSystem sut;
EXPECT_TRUE(sut.Setup(nullptr));
EXPECT_DEATH(sut.Setup(nullptr), "");
}
TEST_F(BasePThreadTLSSystemDeathTest, VerifyDeathIfTearDownWithoutSetup) {
internal::PThreadTLSSystem sut;
EXPECT_DEATH(sut.TearDownForTesting(), "");
}
#endif
} // namespace base::allocator::dispatcher
#endif // USE_LOCAL_TLS_EMULATION()