xref: /aosp_15_r20/external/libcxxabi/test/guard_threaded_test.pass.cpp (revision c05d8e5dc3e10f6ce4317e8bc22cc4a25f55fa94)
1 //===----------------------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 // UNSUPPORTED: c++98, c++03
10 // UNSUPPORTED: libcxxabi-no-threads, libcxxabi-no-exceptions
11 
12 #define TESTING_CXA_GUARD
13 #include "../src/cxa_guard_impl.h"
14 #include <unordered_map>
15 #include <thread>
16 #include <atomic>
17 #include <array>
18 #include <cassert>
19 #include <memory>
20 #include <vector>
21 
22 
23 using namespace __cxxabiv1;
24 
25 enum class InitResult {
26   COMPLETE,
27   PERFORMED,
28   WAITED,
29   ABORTED
30 };
31 constexpr InitResult COMPLETE = InitResult::COMPLETE;
32 constexpr InitResult PERFORMED = InitResult::PERFORMED;
33 constexpr InitResult WAITED = InitResult::WAITED;
34 constexpr InitResult ABORTED = InitResult::ABORTED;
35 
36 
37 template <class Impl, class GuardType, class Init>
check_guard(GuardType * g,Init init)38 InitResult check_guard(GuardType *g, Init init) {
39   uint8_t *first_byte = reinterpret_cast<uint8_t*>(g);
40   if (std::__libcpp_atomic_load(first_byte, std::_AO_Acquire) == 0) {
41     Impl impl(g);
42     if (impl.cxa_guard_acquire() == INIT_IS_PENDING) {
43 #ifndef LIBCXXABI_HAS_NO_EXCEPTIONS
44       try {
45 #endif
46         init();
47         impl.cxa_guard_release();
48         return PERFORMED;
49 #ifndef LIBCXXABI_HAS_NO_EXCEPTIONS
50       } catch (...) {
51         impl.cxa_guard_abort();
52         return ABORTED;
53       }
54 #endif
55     }
56     return WAITED;
57   }
58   return COMPLETE;
59 }
60 
61 
62 template <class GuardType, class Impl>
63 struct FunctionLocalStatic {
FunctionLocalStaticFunctionLocalStatic64   FunctionLocalStatic() { reset(); }
65   FunctionLocalStatic(FunctionLocalStatic const&) = delete;
66 
67   template <class InitFunc>
accessFunctionLocalStatic68   InitResult access(InitFunc&& init) {
69     ++waiting_threads;
70     auto res = check_guard<Impl>(&guard_object, init);
71     --waiting_threads;
72     ++result_counts[static_cast<int>(res)];
73     return res;
74   }
75 
76   struct Accessor {
AccessorFunctionLocalStatic::Accessor77     explicit Accessor(FunctionLocalStatic& obj) : this_obj(&obj) {}
78 
79     template <class InitFn>
operator ()FunctionLocalStatic::Accessor80     void operator()(InitFn && fn) const {
81       this_obj->access(std::forward<InitFn>(fn));
82     }
83   private:
84     FunctionLocalStatic *this_obj;
85   };
86 
get_accessFunctionLocalStatic87   Accessor get_access() {
88     return Accessor(*this);
89   }
90 
resetFunctionLocalStatic91   void reset() {
92     guard_object = 0;
93     waiting_threads.store(0);
94     for (auto& counter : result_counts) {
95       counter.store(0);
96     }
97   }
98 
get_countFunctionLocalStatic99   int get_count(InitResult I) const {
100     return result_counts[static_cast<int>(I)].load();
101   }
num_completedFunctionLocalStatic102   int num_completed() const {
103     return get_count(COMPLETE) + get_count(PERFORMED) + get_count(WAITED);
104   }
num_waitingFunctionLocalStatic105   int num_waiting() const {
106     return waiting_threads.load();
107   }
108 
109 private:
110   GuardType guard_object;
111   std::atomic<int> waiting_threads;
112   std::array<std::atomic<int>, 4> result_counts;
113   static_assert(static_cast<int>(ABORTED) == 3, "only 4 result kinds expected");
114 };
115 
116 struct ThreadGroup {
117   ThreadGroup() = default;
118   ThreadGroup(ThreadGroup const&) = delete;
119 
120   template <class ...Args>
CreateThreadGroup121   void Create(Args&& ...args) {
122     threads.emplace_back(std::forward<Args>(args)...);
123   }
124 
JoinAllThreadGroup125   void JoinAll() {
126     for (auto& t : threads) {
127       t.join();
128     }
129   }
130 
131 private:
132   std::vector<std::thread> threads;
133 };
134 
135 struct Barrier {
BarrierBarrier136   explicit Barrier(int n) : m_wait_for(n) { reset(); }
137   Barrier(Barrier const&) = delete;
138 
waitBarrier139   void wait() {
140     ++m_entered;
141     while (m_entered.load() < m_wait_for) {
142       std::this_thread::yield();
143     }
144     assert(m_entered.load() == m_wait_for);
145     ++m_exited;
146   }
147 
num_waitingBarrier148   int num_waiting() const {
149     return m_entered.load() - m_exited.load();
150   }
151 
resetBarrier152   void reset() {
153     m_entered.store(0);
154     m_exited.store(0);
155   }
156 private:
157   const int m_wait_for;
158   std::atomic<int> m_entered;
159   std::atomic<int> m_exited;
160 };
161 
162 struct Notification {
NotificationNotification163   Notification() { reset(); }
164   Notification(Notification const&) = delete;
165 
num_waitingNotification166   int num_waiting() const {
167     return m_waiting.load();
168   }
169 
waitNotification170   void wait() {
171     if (m_cond.load())
172       return;
173     ++m_waiting;
174     while (!m_cond.load()) {
175       std::this_thread::yield();
176     }
177     --m_waiting;
178   }
179 
notifyNotification180   void notify() {
181     m_cond.store(true);
182   }
183 
184   template <class Cond>
notify_whenNotification185   void notify_when(Cond &&c) {
186     if (m_cond.load())
187       return;
188     while (!c()) {
189       std::this_thread::yield();
190     }
191     m_cond.store(true);
192   }
193 
resetNotification194   void reset() {
195     m_cond.store(0);
196     m_waiting.store(0);
197   }
198 private:
199   std::atomic<bool> m_cond;
200   std::atomic<int> m_waiting;
201 };
202 
203 
204 template <class GuardType, class Impl>
test_free_for_all()205 void test_free_for_all() {
206   const int num_waiting_threads = 10; // one initializing thread, 10 waiters.
207 
208   FunctionLocalStatic<GuardType, Impl> test_obj;
209 
210   Barrier start_init_barrier(num_waiting_threads);
211   bool already_init = false;
212   ThreadGroup threads;
213   for (int i=0; i < num_waiting_threads; ++i) {
214     threads.Create([&]() {
215       start_init_barrier.wait();
216       test_obj.access([&]() {
217         assert(!already_init);
218         already_init = true;
219       });
220     });
221   }
222 
223   // wait for the other threads to finish initialization.
224   threads.JoinAll();
225 
226   assert(test_obj.get_count(PERFORMED) == 1);
227   assert(test_obj.get_count(COMPLETE) + test_obj.get_count(WAITED) == 9);
228 }
229 
230 template <class GuardType, class Impl>
test_waiting_for_init()231 void test_waiting_for_init() {
232     const int num_waiting_threads = 10; // one initializing thread, 10 waiters.
233 
234     Notification init_pending;
235     Notification init_barrier;
236     FunctionLocalStatic<GuardType, Impl> test_obj;
237     auto access_fn = test_obj.get_access();
238 
239     ThreadGroup threads;
240     threads.Create(access_fn,
241       [&]() {
242         init_pending.notify();
243         init_barrier.wait();
244       }
245     );
246     init_pending.wait();
247 
248     assert(test_obj.num_waiting() == 1);
249 
250     for (int i=0; i < num_waiting_threads; ++i) {
251       threads.Create(access_fn, []() { assert(false); });
252     }
253     // unblock the initializing thread
254     init_barrier.notify_when([&]() {
255       return test_obj.num_waiting() == num_waiting_threads + 1;
256     });
257 
258     // wait for the other threads to finish initialization.
259     threads.JoinAll();
260 
261     assert(test_obj.get_count(PERFORMED) == 1);
262     assert(test_obj.get_count(WAITED) == 10);
263     assert(test_obj.get_count(COMPLETE) == 0);
264 }
265 
266 
267 template <class GuardType, class Impl>
test_aborted_init()268 void test_aborted_init() {
269   const int num_waiting_threads = 10; // one initializing thread, 10 waiters.
270 
271   Notification init_pending;
272   Notification init_barrier;
273   FunctionLocalStatic<GuardType, Impl> test_obj;
274   auto access_fn = test_obj.get_access();
275 
276   ThreadGroup threads;
277   threads.Create(access_fn,
278                  [&]() {
279                    init_pending.notify();
280                    init_barrier.wait();
281                    throw 42;
282                  }
283   );
284   init_pending.wait();
285 
286   assert(test_obj.num_waiting() == 1);
287 
288   bool already_init = false;
289   for (int i=0; i < num_waiting_threads; ++i) {
290     threads.Create(access_fn, [&]() {
291       assert(!already_init);
292       already_init = true;
293     });
294   }
295   // unblock the initializing thread
296   init_barrier.notify_when([&]() {
297     return test_obj.num_waiting() == num_waiting_threads + 1;
298   });
299 
300   // wait for the other threads to finish initialization.
301   threads.JoinAll();
302 
303   assert(test_obj.get_count(ABORTED) == 1);
304   assert(test_obj.get_count(PERFORMED) == 1);
305   assert(test_obj.get_count(WAITED) == 9);
306   assert(test_obj.get_count(COMPLETE) == 0);
307 }
308 
309 
310 template <class GuardType, class Impl>
test_completed_init()311 void test_completed_init() {
312   const int num_waiting_threads = 10; // one initializing thread, 10 waiters.
313 
314   Notification init_barrier;
315   FunctionLocalStatic<GuardType, Impl> test_obj;
316 
317   test_obj.access([]() {});
318   assert(test_obj.num_waiting() == 0);
319   assert(test_obj.num_completed() == 1);
320   assert(test_obj.get_count(PERFORMED) == 1);
321 
322   auto access_fn = test_obj.get_access();
323   ThreadGroup threads;
324   for (int i=0; i < num_waiting_threads; ++i) {
325     threads.Create(access_fn, []() {
326       assert(false);
327     });
328   }
329 
330   // wait for the other threads to finish initialization.
331   threads.JoinAll();
332 
333   assert(test_obj.get_count(ABORTED) == 0);
334   assert(test_obj.get_count(PERFORMED) == 1);
335   assert(test_obj.get_count(WAITED) == 0);
336   assert(test_obj.get_count(COMPLETE) == 10);
337 }
338 
339 template <class Impl>
test_impl()340 void test_impl() {
341   {
342     test_free_for_all<uint32_t, Impl>();
343     test_free_for_all<uint32_t, Impl>();
344   }
345   {
346     test_waiting_for_init<uint32_t, Impl>();
347     test_waiting_for_init<uint64_t, Impl>();
348   }
349   {
350     test_aborted_init<uint32_t, Impl>();
351     test_aborted_init<uint64_t, Impl>();
352   }
353   {
354     test_completed_init<uint32_t, Impl>();
355     test_completed_init<uint64_t, Impl>();
356   }
357 }
358 
test_all_impls()359 void test_all_impls() {
360   using MutexImpl = SelectImplementation<Implementation::GlobalLock>::type;
361 
362   // Attempt to test the Futex based implementation if it's supported on the
363   // target platform.
364   using RealFutexImpl = SelectImplementation<Implementation::Futex>::type;
365   using FutexImpl = typename std::conditional<
366       PlatformSupportsFutex(),
367       RealFutexImpl,
368       MutexImpl
369   >::type;
370 
371   // Run each test 5 times to help TSAN catch bugs.
372   const int num_runs = 5;
373   for (int i=0; i < num_runs; ++i) {
374     test_impl<MutexImpl>();
375     if (PlatformSupportsFutex())
376       test_impl<FutexImpl>();
377   }
378 }
379 
380 // A dummy
381 template <bool Dummy = true>
test_futex_syscall()382 void test_futex_syscall() {
383   if (!PlatformSupportsFutex())
384     return;
385   int lock1 = 0;
386   int lock2 = 0;
387   int lock3 = 0;
388   std::thread waiter1([&]() {
389     int expect = 0;
390     PlatformFutexWait(&lock1, expect);
391     assert(lock1 == 1);
392   });
393   std::thread waiter2([&]() {
394     int expect = 0;
395     PlatformFutexWait(&lock2, expect);
396     assert(lock2 == 2);
397   });
398   std::thread waiter3([&]() {
399     int expect = 42; // not the value
400     PlatformFutexWait(&lock3, expect); // doesn't block
401   });
402   std::thread waker([&]() {
403     lock1 = 1;
404     PlatformFutexWake(&lock1);
405     lock2 = 2;
406     PlatformFutexWake(&lock2);
407   });
408   waiter1.join();
409   waiter2.join();
410   waiter3.join();
411   waker.join();
412 }
413 
main()414 int main() {
415   // Test each multi-threaded implementation with real threads.
416   test_all_impls();
417   // Test the basic sanity of the futex syscall wrappers.
418   test_futex_syscall();
419 }
420