588 lines
17 KiB
C++
588 lines
17 KiB
C++
// 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()
|