1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
|
// 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_storage.h"
#include <array>
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/threading/simple_thread.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#include <process.h>
// Ignore warnings about ptr->int conversions that we use when
// storing ints into ThreadLocalStorage.
#pragma warning(disable : 4311 4312)
#endif
namespace base {
#if BUILDFLAG(IS_POSIX)
namespace internal {
// This class is friended by ThreadLocalStorage.
class ThreadLocalStorageTestInternal {
public:
static bool HasBeenDestroyed() {
return ThreadLocalStorage::HasBeenDestroyed();
}
};
} // namespace internal
#endif // BUILDFLAG(IS_POSIX)
namespace {
const int kInitialTlsValue = 0x5555;
const int kFinalTlsValue = 0x7777;
// How many times must a destructor be called before we really are done.
const int kNumberDestructorCallRepetitions = 3;
void ThreadLocalStorageCleanup(void* value);
ThreadLocalStorage::Slot& TLSSlot() {
static NoDestructor<ThreadLocalStorage::Slot> slot(
&ThreadLocalStorageCleanup);
return *slot;
}
class ThreadLocalStorageRunner : public DelegateSimpleThread::Delegate {
public:
explicit ThreadLocalStorageRunner(int* tls_value_ptr)
: tls_value_ptr_(tls_value_ptr) {}
ThreadLocalStorageRunner(const ThreadLocalStorageRunner&) = delete;
ThreadLocalStorageRunner& operator=(const ThreadLocalStorageRunner&) = delete;
~ThreadLocalStorageRunner() override = default;
void Run() override {
*tls_value_ptr_ = kInitialTlsValue;
TLSSlot().Set(tls_value_ptr_);
int* ptr = static_cast<int*>(TLSSlot().Get());
EXPECT_EQ(ptr, tls_value_ptr_);
EXPECT_EQ(*ptr, kInitialTlsValue);
*tls_value_ptr_ = 0;
ptr = static_cast<int*>(TLSSlot().Get());
EXPECT_EQ(ptr, tls_value_ptr_);
EXPECT_EQ(*ptr, 0);
*ptr = kFinalTlsValue + kNumberDestructorCallRepetitions;
}
private:
raw_ptr<int> tls_value_ptr_;
};
void ThreadLocalStorageCleanup(void* value) {
int* ptr = static_cast<int*>(value);
// Destructors should never be called with a NULL.
ASSERT_NE(nullptr, ptr);
if (*ptr == kFinalTlsValue) {
return; // We've been called enough times.
}
ASSERT_LT(kFinalTlsValue, *ptr);
ASSERT_GE(kFinalTlsValue + kNumberDestructorCallRepetitions, *ptr);
--*ptr; // Move closer to our target.
// Tell tls that we're not done with this thread, and still need destruction.
TLSSlot().Set(value);
}
#if BUILDFLAG(IS_POSIX)
constexpr intptr_t kDummyValue = 0xABCD;
constexpr size_t kKeyCount = 20;
// The order in which pthread keys are destructed is not specified by the POSIX
// specification. Hopefully, of the 20 keys we create, some of them should be
// destroyed after the TLS key is destroyed.
class UseTLSDuringDestructionRunner {
public:
UseTLSDuringDestructionRunner() = default;
UseTLSDuringDestructionRunner(const UseTLSDuringDestructionRunner&) = delete;
UseTLSDuringDestructionRunner& operator=(
const UseTLSDuringDestructionRunner&) = delete;
// The order in which pthread_key destructors are called is not well defined.
// Hopefully, by creating 10 both before and after initializing TLS on the
// thread, at least 1 will be called after TLS destruction.
void Run() {
ASSERT_FALSE(internal::ThreadLocalStorageTestInternal::HasBeenDestroyed());
// Create 10 pthread keys before initializing TLS on the thread.
size_t slot_index = 0;
for (; slot_index < 10; ++slot_index) {
CreateTlsKeyWithDestructor(slot_index);
}
// Initialize the Chrome TLS system. It's possible that base::Thread has
// already initialized Chrome TLS, but we don't rely on that.
slot_.Set(reinterpret_cast<void*>(kDummyValue));
// Create 10 pthread keys after initializing TLS on the thread.
for (; slot_index < kKeyCount; ++slot_index) {
CreateTlsKeyWithDestructor(slot_index);
}
}
bool teardown_works_correctly() { return teardown_works_correctly_; }
private:
struct TLSState {
pthread_key_t key;
raw_ptr<bool> teardown_works_correctly;
};
// The POSIX TLS destruction API takes as input a single C-function, which is
// called with the current |value| of a (key, value) pair. We need this
// function to do two things: set the |value| to nullptr, which requires
// knowing the associated |key|, and update the |teardown_works_correctly_|
// state.
//
// To accomplish this, we set the value to an instance of TLSState, which
// contains |key| as well as a pointer to |teardown_works_correctly|.
static void ThreadLocalDestructor(void* value) {
TLSState* state = static_cast<TLSState*>(value);
int result = pthread_setspecific(state->key, nullptr);
ASSERT_EQ(result, 0);
// If this path is hit, then the thread local destructor was called after
// the Chrome-TLS destructor and the internal state was updated correctly.
// No further checks are necessary.
if (internal::ThreadLocalStorageTestInternal::HasBeenDestroyed()) {
*(state->teardown_works_correctly) = true;
return;
}
// If this path is hit, then the thread local destructor was called before
// the Chrome-TLS destructor is hit. The ThreadLocalStorage::Slot should
// still function correctly.
ASSERT_EQ(reinterpret_cast<intptr_t>(slot_.Get()), kDummyValue);
}
void CreateTlsKeyWithDestructor(size_t index) {
ASSERT_LT(index, kKeyCount);
tls_states_[index].teardown_works_correctly = &teardown_works_correctly_;
int result = pthread_key_create(
&(tls_states_[index].key),
UseTLSDuringDestructionRunner::ThreadLocalDestructor);
ASSERT_EQ(result, 0);
result = pthread_setspecific(tls_states_[index].key, &tls_states_[index]);
ASSERT_EQ(result, 0);
}
static base::ThreadLocalStorage::Slot slot_;
bool teardown_works_correctly_ = false;
std::array<TLSState, kKeyCount> tls_states_;
};
base::ThreadLocalStorage::Slot UseTLSDuringDestructionRunner::slot_;
void* UseTLSTestThreadRun(void* input) {
UseTLSDuringDestructionRunner* runner =
static_cast<UseTLSDuringDestructionRunner*>(input);
runner->Run();
return nullptr;
}
#endif // BUILDFLAG(IS_POSIX)
class TlsDestructionOrderRunner : public DelegateSimpleThread::Delegate {
public:
// The runner creates |n_slots| static slots that will be destroyed at
// thread exit, with |spacing| empty slots between them. This allows us to
// test that the destruction order is correct regardless of the actual slot
// indices in the global array.
TlsDestructionOrderRunner(int n_slots, int spacing)
: n_slots_(n_slots), spacing_(spacing) {}
void Run() override {
destructor_calls.clear();
for (int slot = 1; slot < n_slots_ + 1; ++slot) {
for (int i = 0; i < spacing_; ++i) {
ThreadLocalStorage::Slot empty_slot(nullptr);
}
NewStaticTLSSlot(slot);
}
}
static std::vector<int> destructor_calls;
private:
ThreadLocalStorage::Slot& NewStaticTLSSlot(int n) {
NoDestructor<ThreadLocalStorage::Slot> slot(
&TlsDestructionOrderRunner::Destructor);
slot->Set(reinterpret_cast<void*>(n));
return *slot;
}
static void Destructor(void* value) {
int n = reinterpret_cast<intptr_t>(value);
destructor_calls.push_back(n);
}
int n_slots_;
int spacing_;
};
std::vector<int> TlsDestructionOrderRunner::destructor_calls;
class CreateDuringDestructionRunner : public DelegateSimpleThread::Delegate {
public:
void Run() override {
second_destructor_called = false;
NoDestructor<ThreadLocalStorage::Slot> slot(
&CreateDuringDestructionRunner::FirstDestructor);
slot->Set(reinterpret_cast<void*>(123));
}
static bool second_destructor_called;
private:
// The first destructor allocates another TLS slot, which should also be
// destroyed eventually.
static void FirstDestructor(void*) {
NoDestructor<ThreadLocalStorage::Slot> slot(
&CreateDuringDestructionRunner::SecondDestructor);
slot->Set(reinterpret_cast<void*>(234));
}
static void SecondDestructor(void*) { second_destructor_called = true; }
};
bool CreateDuringDestructionRunner::second_destructor_called = false;
} // namespace
TEST(ThreadLocalStorageTest, Basics) {
ThreadLocalStorage::Slot slot;
slot.Set(reinterpret_cast<void*>(123));
int value = reinterpret_cast<intptr_t>(slot.Get());
EXPECT_EQ(value, 123);
}
#if defined(THREAD_SANITIZER)
// Do not run the test under ThreadSanitizer. Because this test iterates its
// own TSD destructor for the maximum possible number of times, TSan can't jump
// in after the last destructor invocation, therefore the destructor remains
// unsynchronized with the following users of the same TSD slot. This results
// in race reports between the destructor and functions in other tests.
#define MAYBE_TLSDestructors DISABLED_TLSDestructors
#else
#define MAYBE_TLSDestructors TLSDestructors
#endif
TEST(ThreadLocalStorageTest, MAYBE_TLSDestructors) {
// Create a TLS index with a destructor. Create a set of
// threads that set the TLS, while the destructor cleans it up.
// After the threads finish, verify that the value is cleaned up.
const int kNumThreads = 5;
std::array<int, kNumThreads> values;
std::array<ThreadLocalStorageRunner*, kNumThreads> thread_delegates;
std::array<DelegateSimpleThread*, kNumThreads> threads;
// Spawn the threads.
for (int index = 0; index < kNumThreads; index++) {
values[index] = kInitialTlsValue;
thread_delegates[index] = new ThreadLocalStorageRunner(&values[index]);
threads[index] =
new DelegateSimpleThread(thread_delegates[index], "tls thread");
threads[index]->Start();
}
// Wait for the threads to finish.
for (int index = 0; index < kNumThreads; index++) {
threads[index]->Join();
delete threads[index];
delete thread_delegates[index];
// Verify that the destructor was called and that we reset.
EXPECT_EQ(values[index], kFinalTlsValue);
}
}
TEST(ThreadLocalStorageTest, TLSReclaim) {
// Creates and destroys many TLS slots and ensures they all zero-inited.
for (int i = 0; i < 1000; ++i) {
ThreadLocalStorage::Slot slot(nullptr);
EXPECT_EQ(nullptr, slot.Get());
slot.Set(reinterpret_cast<void*>(0xBAADF00D));
EXPECT_EQ(reinterpret_cast<void*>(0xBAADF00D), slot.Get());
}
}
#if BUILDFLAG(IS_POSIX)
// Unlike POSIX, Windows does not iterate through the OS TLS to cleanup any
// values there. Instead a per-module thread destruction function is called.
// However, it is not possible to perform a check after this point (as the code
// is detached from the thread), so this check remains POSIX only.
TEST(ThreadLocalStorageTest, UseTLSDuringDestruction) {
UseTLSDuringDestructionRunner runner;
pthread_t thread;
int result = pthread_create(&thread, nullptr, UseTLSTestThreadRun, &runner);
ASSERT_EQ(result, 0);
result = pthread_join(thread, nullptr);
ASSERT_EQ(result, 0);
EXPECT_TRUE(runner.teardown_works_correctly());
}
#endif // BUILDFLAG(IS_POSIX)
// Test that TLS slots are destroyed in the reverse order: the one that was
// created first is destroyed last.
TEST(ThreadLocalStorageTest, DestructionOrder) {
const size_t kNSlots = 5;
const size_t kSpacing = 100;
// The total number of slots is 256, so creating 5 slots with 100 space
// between them will place them in different parts of the slot array.
// This test checks that their destruction order depends only on their
// creation order and not on their index in the array.
TlsDestructionOrderRunner runner(kNSlots, kSpacing);
DelegateSimpleThread thread(&runner, "tls thread");
thread.Start();
thread.Join();
ASSERT_EQ(kNSlots, TlsDestructionOrderRunner::destructor_calls.size());
for (int call = 0, slot = kNSlots; slot > 0; --slot, ++call) {
EXPECT_EQ(slot, TlsDestructionOrderRunner::destructor_calls[call]);
}
}
TEST(ThreadLocalStorageTest, CreateDuringDestruction) {
CreateDuringDestructionRunner runner;
DelegateSimpleThread thread(&runner, "tls thread");
thread.Start();
thread.Join();
ASSERT_TRUE(CreateDuringDestructionRunner::second_destructor_called);
}
} // namespace base
|