
If we're in a blocking call, we need to run the signal immediately, as the call may not return for a very long time (if ever). Not running the handler can cause deadlocks if the rest of the program waits (in one way or another) for the signal handler to execute. I've gone through the list of functions in sanitizer_common_interceptors and marked as blocking those that I know can block, but I don't claim the list to be exhaustive. In particular, I did not mark libc FILE* functions as blocking, because these can end up calling user functions. To do that correctly, /I think/ it would be necessary to clear the "is in blocking call" flag inside the fopencookie wrappers. The test for the bug (deadlock) uses the read call (which is the one that I ran into originally), but the same kind of test could be written for any other blocking syscall.
98 lines
2.3 KiB
C
98 lines
2.3 KiB
C
// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t > %t.out
|
|
// RUN: FileCheck %s --check-prefixes=CHECK,PARENT --input-file %t.out
|
|
// RUN: FileCheck %s --check-prefixes=CHECK,CHILD --input-file %t.out
|
|
// Regression test for
|
|
// https://groups.google.com/g/thread-sanitizer/c/TQrr4-9PRYo/m/HFR4FMi6AQAJ
|
|
#include "test.h"
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
long glob = 0;
|
|
|
|
void *worker(void *main) {
|
|
glob++;
|
|
// synchronize with main
|
|
barrier_wait(&barrier);
|
|
// synchronize with atfork
|
|
barrier_wait(&barrier);
|
|
pthread_kill((pthread_t)main, SIGPROF);
|
|
barrier_wait(&barrier);
|
|
// synchronize with afterfork
|
|
barrier_wait(&barrier);
|
|
pthread_kill((pthread_t)main, SIGPROF);
|
|
barrier_wait(&barrier);
|
|
return NULL;
|
|
}
|
|
|
|
void atfork() {
|
|
write(2, "in atfork\n", strlen("in atfork\n"));
|
|
barrier_wait(&barrier);
|
|
barrier_wait(&barrier);
|
|
static volatile long a;
|
|
__atomic_fetch_add(&a, 1, __ATOMIC_RELEASE);
|
|
}
|
|
|
|
void afterfork() {
|
|
write(2, "in afterfork\n", strlen("in afterfork\n"));
|
|
barrier_wait(&barrier);
|
|
barrier_wait(&barrier);
|
|
static volatile long a;
|
|
__atomic_fetch_add(&a, 1, __ATOMIC_RELEASE);
|
|
}
|
|
|
|
void afterfork_child() {
|
|
write(2, "in afterfork_child\n", strlen("in afterfork_child\n"));
|
|
glob++;
|
|
}
|
|
|
|
void handler(int sig) {
|
|
write(2, "in handler\n", strlen("in handler\n"));
|
|
glob++;
|
|
}
|
|
|
|
int main() {
|
|
barrier_init(&barrier, 2);
|
|
struct sigaction act = {};
|
|
act.sa_handler = &handler;
|
|
if (sigaction(SIGPROF, &act, 0)) {
|
|
perror("sigaction");
|
|
exit(1);
|
|
}
|
|
pthread_atfork(atfork, afterfork, afterfork_child);
|
|
pthread_t t;
|
|
pthread_create(&t, NULL, worker, (void *)pthread_self());
|
|
barrier_wait(&barrier);
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
fprintf(stderr, "fork failed: %d\n", errno);
|
|
return 1;
|
|
}
|
|
if (pid == 0) {
|
|
fprintf(stderr, "CHILD\n");
|
|
return 0;
|
|
}
|
|
if (pid != waitpid(pid, NULL, 0)) {
|
|
fprintf(stderr, "waitpid failed: %d\n", errno);
|
|
return 1;
|
|
}
|
|
pthread_join(t, NULL);
|
|
fprintf(stderr, "PARENT\n");
|
|
return 0;
|
|
}
|
|
|
|
// CHECK: in atfork
|
|
// CHECK: in handler
|
|
// CHECK: ThreadSanitizer: data race
|
|
// CHECK: Write of size 8
|
|
// CHECK: #0 handler
|
|
// CHECK: Previous write of size 8
|
|
// CHECK: #0 worker
|
|
// PARENT: afterfork
|
|
// PARENT: in handler
|
|
// CHILD: afterfork_child
|
|
// CHILD: CHILD
|
|
// CHECK: PARENT
|