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