| //===-- 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_platform.h" |
| #if SANITIZER_LINUX && defined(__x86_64__) |
| |
| #include "sanitizer_common/sanitizer_stoptheworld.h" |
| #include "gtest/gtest.h" |
| |
| #include "sanitizer_common/sanitizer_libc.h" |
| #include "sanitizer_common/sanitizer_common.h" |
| |
| #include <pthread.h> |
| #include <sched.h> |
| |
| namespace __sanitizer { |
| |
| static pthread_mutex_t incrementer_thread_exit_mutex; |
| |
| struct CallbackArgument { |
| volatile int counter; |
| volatile bool threads_stopped; |
| volatile bool callback_executed; |
| CallbackArgument() |
| : counter(0), |
| threads_stopped(false), |
| callback_executed(false) {} |
| }; |
| |
| void *IncrementerThread(void *argument) { |
| CallbackArgument *callback_argument = (CallbackArgument *)argument; |
| while (true) { |
| __sync_fetch_and_add(&callback_argument->counter, 1); |
| if (pthread_mutex_trylock(&incrementer_thread_exit_mutex) == 0) { |
| pthread_mutex_unlock(&incrementer_thread_exit_mutex); |
| return NULL; |
| } else { |
| sched_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 = __sync_fetch_and_add(&callback_argument->counter, 0); |
| for (uptr i = 0; i < 1000; i++) { |
| sched_yield(); |
| if (__sync_fetch_and_add(&callback_argument->counter, 0) != |
| counter_at_init) { |
| callback_argument->threads_stopped = false; |
| return; |
| } |
| } |
| callback_argument->threads_stopped = true; |
| } |
| |
| TEST(StopTheWorld, SuspendThreadsSimple) { |
| pthread_mutex_init(&incrementer_thread_exit_mutex, NULL); |
| CallbackArgument argument; |
| pthread_t thread_id; |
| int pthread_create_result; |
| pthread_mutex_lock(&incrementer_thread_exit_mutex); |
| pthread_create_result = pthread_create(&thread_id, NULL, IncrementerThread, |
| &argument); |
| ASSERT_EQ(0, pthread_create_result); |
| StopTheWorld(&Callback, &argument); |
| pthread_mutex_unlock(&incrementer_thread_exit_mutex); |
| 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_EQ(0, pthread_join(thread_id, NULL)); |
| pthread_mutex_destroy(&incrementer_thread_exit_mutex); |
| } |
| |
| // 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 |
| |
| static pthread_mutex_t advanced_incrementer_thread_exit_mutex; |
| |
| struct AdvancedCallbackArgument { |
| volatile uptr thread_index; |
| volatile int counters[kThreadCount]; |
| pthread_t thread_ids[kThreadCount]; |
| volatile bool threads_stopped; |
| volatile bool callback_executed; |
| volatile bool fatal_error; |
| AdvancedCallbackArgument() |
| : thread_index(0), |
| threads_stopped(false), |
| callback_executed(false), |
| fatal_error(false) {} |
| }; |
| |
| void *AdvancedIncrementerThread(void *argument) { |
| AdvancedCallbackArgument *callback_argument = |
| (AdvancedCallbackArgument *)argument; |
| uptr this_thread_index = __sync_fetch_and_add( |
| &callback_argument->thread_index, 1); |
| // Spawn the next thread. |
| int pthread_create_result; |
| if (this_thread_index + 1 < kThreadCount) { |
| pthread_create_result = |
| pthread_create(&callback_argument->thread_ids[this_thread_index + 1], |
| NULL, AdvancedIncrementerThread, argument); |
| // Cannot use ASSERT_EQ in non-void-returning functions. If there's a |
| // problem, defer failing to the main thread. |
| if (pthread_create_result != 0) { |
| callback_argument->fatal_error = true; |
| __sync_fetch_and_add(&callback_argument->thread_index, |
| kThreadCount - callback_argument->thread_index); |
| } |
| } |
| // Do the actual work. |
| while (true) { |
| __sync_fetch_and_add(&callback_argument->counters[this_thread_index], 1); |
| if (pthread_mutex_trylock(&advanced_incrementer_thread_exit_mutex) == 0) { |
| pthread_mutex_unlock(&advanced_incrementer_thread_exit_mutex); |
| return NULL; |
| } else { |
| sched_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] = __sync_fetch_and_add(&callback_argument->counters[j], |
| 0); |
| for (uptr i = 0; i < 10; i++) { |
| sched_yield(); |
| for (uptr j = 0; j < kThreadCount; j++) |
| if (__sync_fetch_and_add(&callback_argument->counters[j], 0) != |
| counters_at_init[j]) { |
| callback_argument->threads_stopped = false; |
| return; |
| } |
| } |
| callback_argument->threads_stopped = true; |
| } |
| |
| TEST(StopTheWorld, SuspendThreadsAdvanced) { |
| pthread_mutex_init(&advanced_incrementer_thread_exit_mutex, NULL); |
| AdvancedCallbackArgument argument; |
| |
| pthread_mutex_lock(&advanced_incrementer_thread_exit_mutex); |
| int pthread_create_result; |
| pthread_create_result = pthread_create(&argument.thread_ids[0], NULL, |
| AdvancedIncrementerThread, |
| &argument); |
| ASSERT_EQ(0, pthread_create_result); |
| // Wait for several threads to spawn before proceeding. |
| while (__sync_fetch_and_add(&argument.thread_index, 0) < kStopWorldAfter) |
| sched_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 (__sync_fetch_and_add(&argument.thread_index, 0) < kThreadCount) |
| sched_yield(); |
| ASSERT_FALSE(argument.fatal_error); // a pthread_create has failed |
| // Signal the threads to terminate. |
| pthread_mutex_unlock(&advanced_incrementer_thread_exit_mutex); |
| for (uptr i = 0; i < kThreadCount; i++) |
| ASSERT_EQ(0, pthread_join(argument.thread_ids[i], NULL)); |
| pthread_mutex_destroy(&advanced_incrementer_thread_exit_mutex); |
| } |
| |
| static void SegvCallback(const SuspendedThreadsList &suspended_threads_list, |
| void *argument) { |
| *(volatile int*)0x1234 = 0; |
| } |
| |
| TEST(StopTheWorld, SegvInCallback) { |
| // Test that tracer thread catches SIGSEGV. |
| StopTheWorld(&SegvCallback, NULL); |
| } |
| |
| } // namespace __sanitizer |
| |
| #endif // SANITIZER_LINUX && defined(__x86_64__) |