forked from OSchip/llvm-project
172 lines
5.2 KiB
C++
172 lines
5.2 KiB
C++
//===-- 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__)
|