llvm-project/compiler-rt/test/tsan/pthread_atfork_deadlock3.c
Pavel Labath cf1319f9c6
[compiler-rt] Mark more calls as blocking (#77789)
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.
2024-03-11 07:44:26 +01:00

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