diff --git a/compiler-rt/lib/tsan/rtl/tsan_interceptors.cc b/compiler-rt/lib/tsan/rtl/tsan_interceptors.cc
index b157de21a780..62c96cb42047 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interceptors.cc
+++ b/compiler-rt/lib/tsan/rtl/tsan_interceptors.cc
@@ -295,6 +295,20 @@ ScopedInterceptor::~ScopedInterceptor() {
   }
 }
 
+void ScopedInterceptor::UserCallbackStart() {
+  if (in_ignored_lib_) {
+    thr_->in_ignored_lib = false;
+    ThreadIgnoreEnd(thr_, pc_);
+  }
+}
+
+void ScopedInterceptor::UserCallbackEnd() {
+  if (in_ignored_lib_) {
+    thr_->in_ignored_lib = true;
+    ThreadIgnoreBegin(thr_, pc_);
+  }
+}
+
 #define TSAN_INTERCEPT(func) INTERCEPT_FUNCTION(func)
 #if SANITIZER_FREEBSD
 # define TSAN_INTERCEPT_VER(func, ver) INTERCEPT_FUNCTION(func)
diff --git a/compiler-rt/lib/tsan/rtl/tsan_interceptors.h b/compiler-rt/lib/tsan/rtl/tsan_interceptors.h
index 451e8645fc82..d831620cfafe 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interceptors.h
+++ b/compiler-rt/lib/tsan/rtl/tsan_interceptors.h
@@ -10,6 +10,8 @@ class ScopedInterceptor {
  public:
   ScopedInterceptor(ThreadState *thr, const char *fname, uptr pc);
   ~ScopedInterceptor();
+  void UserCallbackStart();
+  void UserCallbackEnd();
  private:
   ThreadState *const thr_;
   const uptr pc_;
@@ -36,6 +38,12 @@ class ScopedInterceptor {
       return REAL(func)(__VA_ARGS__); \
 /**/
 
+#define SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START() \
+    si.UserCallbackStart();
+
+#define SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END() \
+    si.UserCallbackEnd();
+
 #define TSAN_INTERCEPTOR(ret, func, ...) INTERCEPTOR(ret, func, __VA_ARGS__)
 
 #if SANITIZER_FREEBSD
diff --git a/compiler-rt/lib/tsan/rtl/tsan_libdispatch_mac.cc b/compiler-rt/lib/tsan/rtl/tsan_libdispatch_mac.cc
index 391b3f67bad0..617dc91b33d0 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_libdispatch_mac.cc
+++ b/compiler-rt/lib/tsan/rtl/tsan_libdispatch_mac.cc
@@ -94,7 +94,9 @@ static void dispatch_callback_wrap_acquire(void *param) {
   // In serial queues, work items can be executed on different threads, we need
   // to explicitly synchronize on the queue itself.
   if (IsQueueSerial(context->queue)) Acquire(thr, pc, (uptr)context->queue);
+  SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START();
   context->orig_work(context->orig_context);
+  SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END();
   if (IsQueueSerial(context->queue)) Release(thr, pc, (uptr)context->queue);
   user_free(thr, pc, context);
 }
@@ -108,11 +110,15 @@ static void invoke_and_release_block(void *param) {
 #define DISPATCH_INTERCEPT_B(name)                                           \
   TSAN_INTERCEPTOR(void, name, dispatch_queue_t q, dispatch_block_t block) { \
     SCOPED_TSAN_INTERCEPTOR(name, q, block);                                 \
+    SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \
     dispatch_block_t heap_block = Block_copy(block);                         \
+    SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \
     tsan_block_context_t *new_context =                                      \
         AllocContext(thr, pc, q, heap_block, &invoke_and_release_block);     \
     Release(thr, pc, (uptr)new_context);                                     \
+    SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \
     REAL(name##_f)(q, new_context, dispatch_callback_wrap_acquire);          \
+    SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \
   }
 
 #define DISPATCH_INTERCEPT_F(name)                                \
@@ -122,7 +128,9 @@ static void invoke_and_release_block(void *param) {
     tsan_block_context_t *new_context =                           \
         AllocContext(thr, pc, q, context, work);                  \
     Release(thr, pc, (uptr)new_context);                          \
+    SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START(); \
     REAL(name)(q, new_context, dispatch_callback_wrap_acquire);   \
+    SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END(); \
   }
 
 // We wrap dispatch_async, dispatch_sync and friends where we allocate a new
@@ -158,7 +166,9 @@ TSAN_INTERCEPTOR(void, dispatch_once, dispatch_once_t *predicate,
   u32 v = atomic_load(a, memory_order_acquire);
   if (v == 0 &&
       atomic_compare_exchange_strong(a, &v, 1, memory_order_relaxed)) {
+    SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START();
     block();
+    SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END();
     Release(thr, pc, (uptr)a);
     atomic_store(a, 2, memory_order_release);
   } else {
@@ -174,9 +184,11 @@ TSAN_INTERCEPTOR(void, dispatch_once, dispatch_once_t *predicate,
 TSAN_INTERCEPTOR(void, dispatch_once_f, dispatch_once_t *predicate,
                  void *context, dispatch_function_t function) {
   SCOPED_TSAN_INTERCEPTOR(dispatch_once_f, predicate, context, function);
+  SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START();
   WRAP(dispatch_once)(predicate, ^(void) {
     function(context);
   });
+  SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END();
 }
 
 TSAN_INTERCEPTOR(long_t, dispatch_semaphore_signal,
@@ -236,7 +248,9 @@ TSAN_INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group,
 TSAN_INTERCEPTOR(void, dispatch_group_notify, dispatch_group_t group,
                  dispatch_queue_t q, dispatch_block_t block) {
   SCOPED_TSAN_INTERCEPTOR(dispatch_group_notify, group, q, block);
+  SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_START();
   dispatch_block_t heap_block = Block_copy(block);
+  SCOPED_TSAN_INTERCEPTOR_USER_CALLBACK_END();
   tsan_block_context_t *new_context =
       AllocContext(thr, pc, q, heap_block, &invoke_and_release_block);
   new_context->object_to_acquire = (uptr)group;