Index: compiler-rt/trunk/lib/tsan/rtl/tsan_libdispatch_mac.cc =================================================================== --- compiler-rt/trunk/lib/tsan/rtl/tsan_libdispatch_mac.cc +++ compiler-rt/trunk/lib/tsan/rtl/tsan_libdispatch_mac.cc @@ -34,6 +34,7 @@ void *orig_context; dispatch_function_t orig_work; uptr object_to_acquire; + dispatch_object_t object_to_release; } tsan_block_context_t; // The offsets of different fields of the dispatch_queue_t structure, exported @@ -75,6 +76,7 @@ new_context->orig_context = orig_context; new_context->orig_work = orig_work; new_context->object_to_acquire = (uptr)new_context; + new_context->object_to_release = nullptr; return new_context; } @@ -82,6 +84,13 @@ SCOPED_INTERCEPTOR_RAW(dispatch_async_f_callback_wrap); tsan_block_context_t *context = (tsan_block_context_t *)param; Acquire(thr, pc, context->object_to_acquire); + + // Extra retain/release is required for dispatch groups. We use the group + // itself to synchronize, but in a notification (dispatch_group_notify + // callback), it may be disposed already. To solve this, we retain the group + // and release it here. + if (context->object_to_release) dispatch_release(context->object_to_release); + // 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); @@ -231,6 +240,11 @@ tsan_block_context_t *new_context = AllocContext(thr, pc, q, heap_block, &invoke_and_release_block); new_context->object_to_acquire = (uptr)group; + + // Will be released in dispatch_callback_wrap_acquire. + new_context->object_to_release = group; + dispatch_retain(group); + Release(thr, pc, (uptr)group); REAL(dispatch_group_notify_f)(group, q, new_context, dispatch_callback_wrap_acquire); @@ -241,6 +255,11 @@ SCOPED_TSAN_INTERCEPTOR(dispatch_group_notify_f, group, q, context, work); tsan_block_context_t *new_context = AllocContext(thr, pc, q, context, work); new_context->object_to_acquire = (uptr)group; + + // Will be released in dispatch_callback_wrap_acquire. + new_context->object_to_release = group; + dispatch_retain(group); + Release(thr, pc, (uptr)group); REAL(dispatch_group_notify_f)(group, q, new_context, dispatch_callback_wrap_acquire); Index: compiler-rt/trunk/test/tsan/Darwin/gcd-groups-stress.mm =================================================================== --- compiler-rt/trunk/test/tsan/Darwin/gcd-groups-stress.mm +++ compiler-rt/trunk/test/tsan/Darwin/gcd-groups-stress.mm @@ -0,0 +1,43 @@ +// RUN: %clang_tsan %s -o %t -framework Foundation +// RUN: %run %t 2>&1 + +#import + +void notify_callback(void *context) { + // Do nothing. +} + +int main() { + NSLog(@"Hello world."); + + dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + for (int i = 0; i < 300000; i++) { + dispatch_group_t g = dispatch_group_create(); + dispatch_group_enter(g); + dispatch_async(q, ^{ + dispatch_group_leave(g); + }); + dispatch_group_notify(g, q, ^{ + // Do nothing. + }); + dispatch_release(g); + } + + for (int i = 0; i < 300000; i++) { + dispatch_group_t g = dispatch_group_create(); + dispatch_group_enter(g); + dispatch_async(q, ^{ + dispatch_group_leave(g); + }); + dispatch_group_notify_f(g, q, nullptr, ¬ify_callback); + dispatch_release(g); + } + + NSLog(@"Done."); +} + +// CHECK: Hello world. +// CHECK: Done. +// CHECK-NOT: WARNING: ThreadSanitizer +// CHECK-NOT: CHECK failed