1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
//===-- main.cpp ------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// This test is intended to create a situation in which one thread will exit
// while a breakpoint is being handled in another thread. This may not always
// happen because it's possible that the exiting thread will exit before the
// breakpoint is hit. The test case should be flexible enough to treat that
// as success.
#include <atomic>
#include <chrono>
#include <thread>
volatile int g_test = 0;
// Note that although hogging the CPU while waiting for a variable to change
// would be terrible in production code, it's great for testing since it
// avoids a lot of messy context switching to get multiple threads synchronized.
#define do_nothing()
#define pseudo_barrier_wait(bar) \
--bar; \
while (bar > 0) \
do_nothing();
#define pseudo_barrier_init(bar, count) (bar = count)
// A barrier to synchronize all the threads except the one that will exit.
std::atomic_int g_barrier1;
// A barrier to synchronize all the threads including the one that will exit.
std::atomic_int g_barrier2;
// A barrier to keep the first group of threads from exiting until after the
// breakpoint has been passed.
std::atomic_int g_barrier3;
void *
break_thread_func ()
{
// Wait until the entire first group of threads is running
pseudo_barrier_wait(g_barrier1);
// Wait for the exiting thread to start
pseudo_barrier_wait(g_barrier2);
// Do something
g_test++; // Set breakpoint here
// Synchronize after the breakpoint
pseudo_barrier_wait(g_barrier3);
// Return
return NULL;
}
void *
wait_thread_func ()
{
// Wait until the entire first group of threads is running
pseudo_barrier_wait(g_barrier1);
// Wait for the exiting thread to start
pseudo_barrier_wait(g_barrier2);
// Wait until the breakpoint has been passed
pseudo_barrier_wait(g_barrier3);
// Return
return NULL;
}
void *
exit_thread_func ()
{
// Sync up with the rest of the threads.
pseudo_barrier_wait(g_barrier2);
// Try to make sure this thread doesn't exit until the breakpoint is hit.
std::this_thread::sleep_for(std::chrono::microseconds(1));
// Return
return NULL;
}
int main ()
{
// The first barrier waits for the non-exiting threads to start.
// This thread will also participate in that barrier.
// The idea here is to guarantee that the exiting thread will be
// last in the internal list maintained by the debugger.
pseudo_barrier_init(g_barrier1, 5);
// The second break synchronyizes thread exection with the breakpoint.
pseudo_barrier_init(g_barrier2, 5);
// The third barrier keeps the waiting threads around until the breakpoint
// has been passed.
pseudo_barrier_init(g_barrier3, 4);
// Create a thread to hit the breakpoint
std::thread thread_1(break_thread_func);
// Create more threads to slow the debugger down during processing.
std::thread thread_2(wait_thread_func);
std::thread thread_3(wait_thread_func);
std::thread thread_4(wait_thread_func);
// Wait for all these threads to get started.
pseudo_barrier_wait(g_barrier1);
// Create a thread to exit during the breakpoint
std::thread thread_5(exit_thread_func);
// Wait for the threads to finish
thread_5.join();
thread_4.join();
thread_3.join();
thread_2.join();
thread_1.join();
return 0;
}
|