llvm-project/compiler-rt/test/tsan/fork_multithreaded.cpp
Dmitry Vyukov d95baa98f3 tsan: fix failures after multi-threaded fork
Creating threads after a multi-threaded fork is semi-supported,
we don't give particular guarantees, but we try to not fail
on simple cases and we have die_after_fork=0 flag that enables
not dying on creation of threads after a multi-threaded fork.
This flag is used in the wild:
23c052e3e3/SConstruct (L3599)

fork_multithreaded.cpp test started hanging in debug mode
after the recent "tsan: fix deadlock during race reporting" commit,
which added proactive ThreadRegistryLock check in SlotLock.

But the test broke earlier after "tsan: remove quadratic behavior in pthread_join"
commit which made tracking of alive threads based on pthread_t stricter
(CHECK-fail on 2 threads with the same pthread_t, or joining a non-existent thread).
When we start a thread after a multi-threaded fork, the new pthread_t
can actually match one of existing values (for threads that don't exist anymore).
Thread creation started CHECK-failing on this, but the test simply
ignored this CHECK failure in the child thread and "passed".
But after "tsan: fix deadlock during race reporting" the test started hanging dead,
because CHECK failures recursively lock thread registry.

Fix this purging all alive threads from thread registry on fork.

Also the thread registry mutex somehow lost the internal deadlock detector id
and was excluded from deadlock detection. If it would have the id, the CHECK
wouldn't hang because of the nested CHECK failure due to the deadlock.
But then again the test would have silently ignore this error as well
and the bugs wouldn't have been noticed.
Add the deadlock detector id to the thread registry mutex.

Also extend the test to check more cases and detect more bugs.

Reviewed By: melver

Differential Revision: https://reviews.llvm.org/D116091
2021-12-21 16:54:00 +01:00

67 lines
1.9 KiB
C++

// RUN: %clangxx_tsan -O1 %s -o %t && %run %t 66 2>&1 | FileCheck %s -check-prefix=CHECK-DIE
// RUN: %clangxx_tsan -O1 %s -o %t && %env_tsan_opts=die_after_fork=0 %run %t 2>&1 | FileCheck %s -check-prefix=CHECK-NODIE
#include "test.h"
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
static void *sleeper(void *p) {
barrier_wait(&barrier);
return 0;
}
static void *nop(void *p) {
return 0;
}
int main(int argc, const char **argv) {
barrier_init(&barrier, 3);
const int kSleeperThreads = 2;
barrier_init(&barrier, kSleeperThreads + 1);
pthread_t th0[kSleeperThreads];
for (int i = 0; i < kSleeperThreads; i++)
pthread_create(&th0[i], 0, sleeper, 0);
const int kNopThreads = 5;
pthread_t th1[kNopThreads];
for (int i = 0; i < kNopThreads; i++)
pthread_create(&th1[i], 0, nop, 0);
for (int i = 0; i < kNopThreads; i++)
pthread_join(th1[i], 0);
int pid = fork();
if (pid < 0) {
fprintf(stderr, "failed to fork (%d)\n", errno);
exit(1);
}
if (pid == 0) {
// child
const int kChildThreads = 4;
pthread_t th2[kChildThreads];
for (int i = 0; i < kChildThreads; i++)
pthread_create(&th2[i], 0, nop, 0);
for (int i = 0; i < kChildThreads; i++)
pthread_join(th2[i], 0);
exit(0);
return 0;
}
// parent
int expect = argc > 1 ? atoi(argv[1]) : 0;
int status = 0;
while (waitpid(pid, &status, 0) != pid) {
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != expect) {
fprintf(stderr, "subprocess exited with %d, expected %d\n", status, expect);
exit(1);
}
barrier_wait(&barrier);
for (int i = 0; i < kSleeperThreads; i++)
pthread_join(th0[i], 0);
fprintf(stderr, "OK\n");
return 0;
}
// CHECK-DIE: ThreadSanitizer: starting new threads after multi-threaded fork is not supported
// CHECK-NODIE-NOT: ThreadSanitizer:
// CHECK-NODIE: OK
// CHECK-NODIE-NOT: ThreadSanitizer: