1// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s
2
3// Test case for recursive signal handlers, adopted from:
4// https://github.com/google/sanitizers/issues/478
5
6// UNSUPPORTED: darwin
7
8#include "test.h"
9#include <semaphore.h>
10#include <signal.h>
11#include <errno.h>
12
13static const int kSigSuspend = SIGUSR1;
14static const int kSigRestart = SIGUSR2;
15
16static sem_t g_thread_suspend_ack_sem;
17
18static volatile bool g_busy_thread_garbage_collected;
19
20static void SaveRegistersInStack() {
21 // Mono walks thread stacks to detect unreferenced objects.
22 // If last object reference is kept in register the object will be collected
23 // This is why threads can't be suspended with something like pthread_suspend
24}
25
26static void fail(const char *what) {
27 fprintf(stderr, format: "FAILED: %s (errno=%d)\n", what, errno);
28 exit(status: 1);
29}
30
31static void CheckSigBlocked(const sigset_t &oldset, const sigset_t &newset,
32 int sig) {
33 const int is_old_member = sigismember(set: &oldset, signo: sig);
34 const int is_new_member = sigismember(set: &newset, signo: sig);
35
36 if (is_old_member == -1 || is_new_member == -1)
37 fail(what: "sigismember failed");
38
39 if (is_old_member != is_new_member)
40 fail(what: "restoring signals failed");
41}
42
43sigset_t GetCurrentSigSet() {
44 sigset_t set;
45 if (sigemptyset(set: &set) != 0)
46 fail(what: "sigemptyset failed");
47
48 if (pthread_sigmask(SIG_BLOCK, NULL, oldmask: &set) != 0)
49 fail(what: "pthread_sigmask failed");
50
51 return set;
52}
53
54static void SuspendHandler(int sig) {
55 int old_errno = errno;
56 SaveRegistersInStack();
57
58 // Enable kSigRestart handling, tsan disables signals around signal handlers.
59 const auto oldset = GetCurrentSigSet();
60
61 // Acknowledge that thread is saved and suspended
62 if (sem_post(sem: &g_thread_suspend_ack_sem) != 0)
63 fail(what: "sem_post failed");
64
65 // Wait for wakeup signal.
66 sigset_t sigset;
67 sigemptyset(set: &sigset);
68 if (sigsuspend(set: &sigset) != 0 && errno != EINTR)
69 fail(what: "sigsuspend failed");
70
71 const auto newset = GetCurrentSigSet();
72
73 // Check that the same signals are blocked as before
74 CheckSigBlocked(oldset, newset, sig: kSigSuspend);
75 CheckSigBlocked(oldset, newset, sig: kSigRestart);
76
77 // Acknowledge that thread restarted
78 if (sem_post(sem: &g_thread_suspend_ack_sem) != 0)
79 fail(what: "sem_post failed");
80
81 g_busy_thread_garbage_collected = true;
82
83 errno = old_errno;
84}
85
86static void RestartHandler(int sig) {}
87
88static void WaitSem() {
89 while (sem_wait(sem: &g_thread_suspend_ack_sem) != 0) {
90 if (errno != EINTR)
91 fail(what: "sem_wait failed");
92 }
93}
94
95static void StopWorld(pthread_t thread) {
96 if (pthread_kill(threadid: thread, signo: kSigSuspend) != 0)
97 fail(what: "pthread_kill failed");
98
99 WaitSem();
100}
101
102static void StartWorld(pthread_t thread) {
103 if (pthread_kill(threadid: thread, signo: kSigRestart) != 0)
104 fail(what: "pthread_kill failed");
105
106 WaitSem();
107}
108
109static void CollectGarbage(pthread_t thread) {
110 // Wait for the thread to start
111 WaitSem();
112
113 StopWorld(thread);
114 // Walk stacks
115 StartWorld(thread);
116}
117
118static void Init() {
119 if (sem_init(sem: &g_thread_suspend_ack_sem, pshared: 0, value: 0) != 0)
120 fail(what: "sem_init failed");
121
122 struct sigaction act = {};
123 act.sa_flags = SA_RESTART;
124 act.sa_handler = &SuspendHandler;
125 if (sigaction(sig: kSigSuspend, act: &act, NULL) != 0)
126 fail(what: "sigaction failed");
127 act.sa_handler = &RestartHandler;
128 if (sigaction(sig: kSigRestart, act: &act, NULL) != 0)
129 fail(what: "sigaction failed");
130}
131
132void* BusyThread(void *arg) {
133 (void)arg;
134 const auto oldset = GetCurrentSigSet();
135
136 if (sem_post(sem: &g_thread_suspend_ack_sem) != 0)
137 fail(what: "sem_post failed");
138
139 while (!g_busy_thread_garbage_collected) {
140 usleep(useconds: 100); // Tsan deadlocks without these sleeps
141 }
142
143 const auto newset = GetCurrentSigSet();
144
145 // Check that we have the same signals blocked as before
146 CheckSigBlocked(oldset, newset, sig: kSigSuspend);
147 CheckSigBlocked(oldset, newset, sig: kSigRestart);
148
149 return NULL;
150}
151
152int main(int argc, const char *argv[]) {
153 Init();
154
155 pthread_t busy_thread;
156 if (pthread_create(newthread: &busy_thread, NULL, start_routine: &BusyThread, NULL) != 0)
157 fail(what: "pthread_create failed");
158
159 CollectGarbage(thread: busy_thread);
160 if (pthread_join(th: busy_thread, thread_return: 0) != 0)
161 fail(what: "pthread_join failed");
162
163 fprintf(stderr, format: "DONE\n");
164
165 return 0;
166}
167
168// CHECK-NOT: FAILED
169// CHECK-NOT: ThreadSanitizer CHECK failed
170// CHECK-NOT: WARNING: ThreadSanitizer:
171// CHECK: DONE
172

source code of compiler-rt/test/tsan/signal_recursive.cpp