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 @@ -34,6 +34,9 @@ __tsan_resumed = 1; } +SANITIZER_WEAK_DEFAULT_IMPL +void __tsan_test_only_on_fork() {} + namespace __tsan { #if !SANITIZER_GO @@ -499,7 +502,11 @@ thr->suppress_reports++; // On OS X, REAL(fork) can call intercepted functions (OSSpinLockLock), and // we'll assert in CheckNoLocks() unless we ignore interceptors. + // On OS X libSystem_atfork_prepare/parent/child callbacks are called + // after/before our callbacks and they call free. thr->ignore_interceptors++; + + __tsan_test_only_on_fork(); } void ForkParentAfter(ThreadState *thr, uptr pc) NO_THREAD_SAFETY_ANALYSIS { diff --git a/compiler-rt/test/tsan/Linux/fork_deadlock.cpp b/compiler-rt/test/tsan/Linux/fork_deadlock.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tsan/Linux/fork_deadlock.cpp @@ -0,0 +1,63 @@ +// RUN: %clangxx_tsan -O1 %s -o %t && %env_tsan_opts=atexit_sleep_ms=0 %run %t 2>&1 | FileCheck %s + +// This test models what happens on Mac when fork +// calls malloc/free inside of our atfork callbacks. +// and ensures that we don't deadlock on malloc/free calls. + +#include "../test.h" +#include "syscall.h" +#include +#include +#include +#include + +void alloc_free_blocks() { + // Allocate a bunch of blocks to drain local allocator cache + // and provoke it to lock allocator global mutexes. + const int kBlocks = 1000; + void *blocks[kBlocks]; + for (int i = 0; i < kBlocks; i++) { + void *p = malloc(10); + *(volatile char *)p = 0; + blocks[i] = p; + } + for (int i = 0; i < kBlocks; i++) + free(blocks[i]); +} + +__attribute__((disable_sanitizer_instrumentation)) extern "C" void +__tsan_test_only_on_fork() { + const char *msg = "__tsan_test_only_on_fork\n"; + write(2, msg, strlen(msg)); + alloc_free_blocks(); +} + +static void *background(void *p) { + for (;;) + alloc_free_blocks(); + return 0; +} + +int main() { + pthread_t th; + pthread_create(&th, 0, background, 0); + pthread_detach(th); + for (int i = 0; i < 10; i++) { + int pid = myfork(); + if (pid < 0) { + fprintf(stderr, "failed to fork (%d)\n", errno); + exit(1); + } + if (pid == 0) { + // child + exit(0); + } + // parent + while (wait(0) < 0) { + } + } + fprintf(stderr, "DONE\n"); +} + +// CHECK: __tsan_test_only_on_fork +// CHECK: DONE