tsan: add another fork test

Add a fork test that models what happens on Mac
where fork calls malloc/free inside of our atfork
callbacks.

Reviewed By: vitalybuka, yln

Differential Revision: https://reviews.llvm.org/D114250

GitOrigin-RevId: 6a3958247aeeacdbf40833151220b089f066c82f
diff --git a/lib/tsan/rtl/tsan_rtl.cpp b/lib/tsan/rtl/tsan_rtl.cpp
index ff7726e..8126a50 100644
--- a/lib/tsan/rtl/tsan_rtl.cpp
+++ b/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/test/tsan/Linux/fork_deadlock.cpp b/test/tsan/Linux/fork_deadlock.cpp
new file mode 100644
index 0000000..8f38ab9
--- /dev/null
+++ b/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 <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+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