| //===-- sanitizer_stoptheworld_test.cpp -----------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Tests for sanitizer_stoptheworld.h |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "sanitizer_common/sanitizer_stoptheworld.h" |
| |
| #include "sanitizer_common/sanitizer_platform.h" |
| #if (SANITIZER_LINUX || SANITIZER_WINDOWS) && defined(__x86_64__) |
| |
| # include <atomic> |
| # include <mutex> |
| # include <thread> |
| |
| # include "gtest/gtest.h" |
| # include "sanitizer_common/sanitizer_common.h" |
| # include "sanitizer_common/sanitizer_libc.h" |
| |
| namespace __sanitizer { |
| |
| static std::mutex mutex; |
| |
| struct CallbackArgument { |
| std::atomic_int counter = {}; |
| std::atomic_bool threads_stopped = {}; |
| std::atomic_bool callback_executed = {}; |
| }; |
| |
| void IncrementerThread(CallbackArgument &callback_argument) { |
| while (true) { |
| callback_argument.counter++; |
| |
| if (mutex.try_lock()) { |
| mutex.unlock(); |
| return; |
| } |
| |
| std::this_thread::yield(); |
| } |
| } |
| |
| // This callback checks that IncrementerThread is suspended at the time of its |
| // execution. |
| void Callback(const SuspendedThreadsList &suspended_threads_list, |
| void *argument) { |
| CallbackArgument *callback_argument = (CallbackArgument *)argument; |
| callback_argument->callback_executed = true; |
| int counter_at_init = callback_argument->counter; |
| for (uptr i = 0; i < 1000; i++) { |
| std::this_thread::yield(); |
| if (callback_argument->counter != counter_at_init) { |
| callback_argument->threads_stopped = false; |
| return; |
| } |
| } |
| callback_argument->threads_stopped = true; |
| } |
| |
| TEST(StopTheWorld, SuspendThreadsSimple) { |
| CallbackArgument argument; |
| std::thread thread; |
| { |
| std::lock_guard<std::mutex> lock(mutex); |
| thread = std::thread(IncrementerThread, std::ref(argument)); |
| StopTheWorld(&Callback, &argument); |
| } |
| EXPECT_TRUE(argument.callback_executed); |
| EXPECT_TRUE(argument.threads_stopped); |
| // argument is on stack, so we have to wait for the incrementer thread to |
| // terminate before we can return from this function. |
| ASSERT_NO_THROW(thread.join()); |
| } |
| |
| // A more comprehensive test where we spawn a bunch of threads while executing |
| // StopTheWorld in parallel. |
| static const uptr kThreadCount = 50; |
| static const uptr kStopWorldAfter = 10; // let this many threads spawn first |
| |
| struct AdvancedCallbackArgument { |
| std::atomic_uintptr_t thread_index = {}; |
| std::atomic_int counters[kThreadCount] = {}; |
| std::thread threads[kThreadCount]; |
| std::atomic_bool threads_stopped = {}; |
| std::atomic_bool callback_executed = {}; |
| }; |
| |
| void AdvancedIncrementerThread(AdvancedCallbackArgument &callback_argument) { |
| uptr this_thread_index = callback_argument.thread_index++; |
| // Spawn the next thread. |
| if (this_thread_index + 1 < kThreadCount) { |
| callback_argument.threads[this_thread_index + 1] = |
| std::thread(AdvancedIncrementerThread, std::ref(callback_argument)); |
| } |
| // Do the actual work. |
| while (true) { |
| callback_argument.counters[this_thread_index]++; |
| if (mutex.try_lock()) { |
| mutex.unlock(); |
| return; |
| } |
| |
| std::this_thread::yield(); |
| } |
| } |
| |
| void AdvancedCallback(const SuspendedThreadsList &suspended_threads_list, |
| void *argument) { |
| AdvancedCallbackArgument *callback_argument = |
| (AdvancedCallbackArgument *)argument; |
| callback_argument->callback_executed = true; |
| |
| int counters_at_init[kThreadCount]; |
| for (uptr j = 0; j < kThreadCount; j++) |
| counters_at_init[j] = callback_argument->counters[j]; |
| for (uptr i = 0; i < 10; i++) { |
| std::this_thread::yield(); |
| for (uptr j = 0; j < kThreadCount; j++) |
| if (callback_argument->counters[j] != counters_at_init[j]) { |
| callback_argument->threads_stopped = false; |
| return; |
| } |
| } |
| callback_argument->threads_stopped = true; |
| } |
| |
| TEST(StopTheWorld, SuspendThreadsAdvanced) { |
| AdvancedCallbackArgument argument; |
| |
| { |
| std::lock_guard<std::mutex> lock(mutex); |
| argument.threads[0] = |
| std::thread(AdvancedIncrementerThread, std::ref(argument)); |
| // Wait for several threads to spawn before proceeding. |
| while (argument.thread_index < kStopWorldAfter) std::this_thread::yield(); |
| StopTheWorld(&AdvancedCallback, &argument); |
| EXPECT_TRUE(argument.callback_executed); |
| EXPECT_TRUE(argument.threads_stopped); |
| |
| // Wait for all threads to spawn before we start terminating them. |
| while (argument.thread_index < kThreadCount) std::this_thread::yield(); |
| } |
| // Signal the threads to terminate. |
| for (auto &t : argument.threads) t.join(); |
| } |
| |
| static void SegvCallback(const SuspendedThreadsList &suspended_threads_list, |
| void *argument) { |
| *(volatile int *)0x1234 = 0; |
| } |
| |
| # if SANITIZER_WINDOWS |
| # define MAYBE_SegvInCallback DISABLED_SegvInCallback |
| # else |
| # define MAYBE_SegvInCallback SegvInCallback |
| # endif |
| |
| TEST(StopTheWorld, MAYBE_SegvInCallback) { |
| // Test that tracer thread catches SIGSEGV. |
| StopTheWorld(&SegvCallback, NULL); |
| } |
| |
| } // namespace __sanitizer |
| |
| #endif // SANITIZER_LINUX && defined(__x86_64__) |