diff --git a/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp b/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp --- a/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp +++ b/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp @@ -81,6 +81,8 @@ extern "C" int pthread_attr_destroy(void *attr); DECLARE_REAL(int, pthread_attr_getdetachstate, void *, void *) extern "C" int pthread_attr_setstacksize(void *attr, uptr stacksize); +extern "C" int pthread_atfork(void (*prepare)(void), void (*parent)(void), + void (*child)(void)); extern "C" int pthread_key_create(unsigned *key, void (*destructor)(void* v)); extern "C" int pthread_setspecific(unsigned key, const void *v); DECLARE_REAL(int, pthread_mutexattr_gettype, void *, void *) @@ -2147,26 +2149,32 @@ if (in_symbolizer()) return REAL(fork)(fake); SCOPED_INTERCEPTOR_RAW(fork, fake); + return REAL(fork)(fake); +} + +void atfork_prepare() { + if (in_symbolizer()) + return; + ThreadState *thr = cur_thread(); + const uptr pc = StackTrace::GetCurrentPc(); ForkBefore(thr, pc); - int pid; - { - // On OS X, REAL(fork) can call intercepted functions (OSSpinLockLock), and - // we'll assert in CheckNoLocks() unless we ignore interceptors. - ScopedIgnoreInterceptors ignore; - pid = REAL(fork)(fake); - } - if (pid == 0) { - // child - ForkChildAfter(thr, pc); - FdOnFork(thr, pc); - } else if (pid > 0) { - // parent - ForkParentAfter(thr, pc); - } else { - // error - ForkParentAfter(thr, pc); - } - return pid; +} + +void atfork_parent() { + if (in_symbolizer()) + return; + ThreadState *thr = cur_thread(); + const uptr pc = StackTrace::GetCurrentPc(); + ForkParentAfter(thr, pc); +} + +void atfork_child() { + if (in_symbolizer()) + return; + ThreadState *thr = cur_thread(); + const uptr pc = StackTrace::GetCurrentPc(); + ForkChildAfter(thr, pc); + FdOnFork(thr, pc); } TSAN_INTERCEPTOR(int, vfork, int fake) { @@ -2841,6 +2849,10 @@ Printf("ThreadSanitizer: failed to setup atexit callback\n"); Die(); } + if (pthread_atfork(atfork_prepare, atfork_parent, atfork_child)) { + Printf("ThreadSanitizer: failed to setup atfork callbacks\n"); + Die(); + } #if !SANITIZER_MAC && !SANITIZER_NETBSD && !SANITIZER_FREEBSD if (pthread_key_create(&interceptor_ctx()->finalize_key, &thread_finalize)) { diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp --- a/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp +++ b/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp @@ -521,20 +521,25 @@ ctx->report_mtx.Lock(); // Suppress all reports in the pthread_atfork callbacks. // Reports will deadlock on the report_mtx. - // We could ignore interceptors and sync operations as well, + // We could ignore sync operations as well, // but so far it's unclear if it will do more good or harm. // Unnecessarily ignoring things can lead to false positives later. thr->suppress_reports++; + // On OS X, REAL(fork) can call intercepted functions (OSSpinLockLock), and + // we'll assert in CheckNoLocks() unless we ignore interceptors. + thr->ignore_interceptors++; } void ForkParentAfter(ThreadState *thr, uptr pc) { thr->suppress_reports--; // Enabled in ForkBefore. + thr->ignore_interceptors--; ctx->report_mtx.Unlock(); ctx->thread_registry->Unlock(); } void ForkChildAfter(ThreadState *thr, uptr pc) { thr->suppress_reports--; // Enabled in ForkBefore. + thr->ignore_interceptors--; ctx->report_mtx.Unlock(); ctx->thread_registry->Unlock(); diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp --- a/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp +++ b/compiler-rt/lib/tsan/rtl/tsan_rtl_report.cpp @@ -156,6 +156,12 @@ case ReportTypeSignalUnsafe: return flags()->report_signal_unsafe; case ReportTypeThreadLeak: +#if !SANITIZER_GO + // It's impossible to join phantom threads + // in the child after fork. + if (ctx->after_multithreaded_fork) + return false; +#endif return flags()->report_thread_leaks; case ReportTypeMutexDestroyLocked: return flags()->report_destroy_locked; diff --git a/compiler-rt/test/tsan/pthread_atfork_deadlock.c b/compiler-rt/test/tsan/pthread_atfork_deadlock.c --- a/compiler-rt/test/tsan/pthread_atfork_deadlock.c +++ b/compiler-rt/test/tsan/pthread_atfork_deadlock.c @@ -21,7 +21,7 @@ int main() { barrier_init(&barrier, 2); - pthread_atfork(atfork, NULL, NULL); + pthread_atfork(atfork, atfork, atfork); pthread_t t; pthread_create(&t, NULL, worker, NULL); glob++; diff --git a/compiler-rt/test/tsan/pthread_atfork_deadlock2.c b/compiler-rt/test/tsan/pthread_atfork_deadlock2.c --- a/compiler-rt/test/tsan/pthread_atfork_deadlock2.c +++ b/compiler-rt/test/tsan/pthread_atfork_deadlock2.c @@ -1,4 +1,4 @@ -// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s // Regression test for // https://groups.google.com/d/msg/thread-sanitizer/e_zB9gYqFHM/DmAiTsrLAwAJ // pthread_atfork() callback triggers a data race and we deadlocked @@ -22,7 +22,7 @@ int main() { barrier_init(&barrier, 2); - pthread_atfork(atfork, NULL, NULL); + pthread_atfork(atfork, atfork, atfork); pthread_t t; pthread_create(&t, NULL, worker, NULL); barrier_wait(&barrier); @@ -44,6 +44,10 @@ return 0; } -// CHECK-NOT: ThreadSanitizer: data race +// CHECK: ThreadSanitizer: data race +// CHECK: Write of size 4 +// CHECK: #0 atfork +// CHECK: Previous write of size 4 +// CHECK: #0 worker // CHECK: CHILD // CHECK: PARENT diff --git a/compiler-rt/test/tsan/pthread_atfork_deadlock3.c b/compiler-rt/test/tsan/pthread_atfork_deadlock3.c --- a/compiler-rt/test/tsan/pthread_atfork_deadlock3.c +++ b/compiler-rt/test/tsan/pthread_atfork_deadlock3.c @@ -1,4 +1,4 @@ -// RUN: %clang_tsan -O1 %s -o %t && %run %t 2>&1 | FileCheck %s +// RUN: %clang_tsan -O1 %s -o %t && %deflake %run %t | FileCheck %s // Regression test for // https://groups.google.com/g/thread-sanitizer/c/TQrr4-9PRYo/m/HFR4FMi6AQAJ #include "test.h" @@ -12,7 +12,13 @@ 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); @@ -27,6 +33,22 @@ __atomic_fetch_add(&a, 1, __ATOMIC_RELEASE); } +void afterfork() { + barrier_wait(&barrier); + barrier_wait(&barrier); + write(2, "in afterfork\n", strlen("in afterfork\n")); + static volatile long a; + __atomic_fetch_add(&a, 1, __ATOMIC_RELEASE); +} + +void afterfork_child() { + // Can't synchronize with barriers because we are + // in the new process, but want consistent output. + sleep(1); + 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++; @@ -40,7 +62,7 @@ perror("sigaction"); exit(1); } - pthread_atfork(atfork, NULL, NULL); + pthread_atfork(atfork, afterfork, afterfork_child); pthread_t t; pthread_create(&t, NULL, worker, (void*)pthread_self()); barrier_wait(&barrier); @@ -64,8 +86,13 @@ // CHECK: in atfork // CHECK: in handler -// Note: There is a race, but we won't report it -// to not deadlock. -// CHECK-NOT: ThreadSanitizer: data race +// CHECK: ThreadSanitizer: data race +// CHECK: Write of size 8 +// CHECK: #0 handler +// CHECK: Previous write of size 8 +// CHECK: #0 worker +// CHECK: afterfork +// CHECK: in handler +// CHECK: afterfork_child // CHECK: CHILD // CHECK: PARENT