Baffling race condition being flagged

Here's the (terse) code of my MTKView render loop.


- (void)drawRect:(NSRect)dirtyRect {
   dispatch_semaphore_wait(self.inflightSemaphore, DISPATCH_TIME_FOREVER);
   [super drawRect:dirtyRect];
...
   [commandBuffer addCompletedHandler:^(id buffer) {
      ...
      dispatch_semaphore_signal(self.inflightSemaphore);  // being flagged here!
   }
}


With thread sanitation turned on, XCode (9.4.1) is flagging the dispatch_semaphore_signal as a 'Data Race detected'.


In my 'applicationDidFinishLaunching' code, I allocate the MTKView (MonitorMTKView and DisplayMTKView) programatically and them as subviews to an NSWindow. This is flagged as a 'Heap block allocated by thread1' and 'Write of size 8 by thread 1' by the race detector. These MTKViews are setup to require manual draw calls (enableSetNeedsDisplay = NO and paused = YES).


After this setup, I create my CVDisplayLink callbacks; I have these issuing the render call, and that's where the semaphore is flagged as a 'Read of Size 8 by thread 5' in a MonitorMTKView instance. I should mention that render output of DisplayMTKView is being passed down to a MonitorMTKView for further processing/display.

How is this even possible? It's a semaphore!


Here's some further debug info:

WARNING: ThreadSanitizer: data race (pid=2876)
  Read of size 8 at 0x7b0c000cd4c0 by thread T35:
    #0 __32-[MonitorMTKView drawRect:]_block_invoke MonitorMTKView.m:225 (CDTest:x86_64+0x100241e5c)
    #1 _doMTLDispatch <null>:11239952 (Metal:x86_64+0x550e9)
    #2 _dispatch_client_callout <null>:11239952 (libdispatch.dylib:x86_64+0x1d8e)

  Previous write of size 8 at 0x7b0c000cd4c0 by thread T30 (mutexes: write M578848238724123704):
    #0 __copy_helper_block_ MonitorMTKView.m:224 (CDTest:x86_64+0x100241f5d)
    #1 _Block_copy <null>:11239952 (libsystem_blocks.dylib:x86_64+0x8ef)
    #2 -[MTKView draw] <null>:11239952 (MetalKit:x86_64+0x134d9)
    #3 -[DisplayMTKView drawRect:] DisplayMTKView.m:647 (CDTest:x86_64+0x1000eb774)
    #4 -[MTKView draw] <null>:11239952 (MetalKit:x86_64+0x134d9)
    #5 cvCallback DisplayCVLinkWrapper.m:228 (CDTest:x86_64+0x1001f2e1c)
    #6 CVDisplayLink::performIO(CVTimeStamp*) <null>:11239952 (CoreVideo:x86_64+0x35ce)

  Location is heap block 2018-10-14 08:49:57.169041-0700 CDTest[2876:386511] Bailed on: NO_ROTATIONAL_BUFFER
of size 40 at 0x7b0c000cd4a0 allocated by thread T30:
2018-10-14 08:49:57.158746-0700 CDTest[2876:386509] Bailed on: NO_ROTATIONAL_BUFFER
2018-10-14 08:49:57.169280-0700 CDTest[2876:386515] Bailed on: NO_ROTATIONAL_BUFFER
    #0 malloc <null>:11239984 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x4998a)
    #1 _Block_copy <null>:11239984 (libsystem_blocks.dylib:x86_64+0x8b2)
    #2 -[MTKView draw] <null>:11239984 (MetalKit:x86_64+0x134d9)
    #3 -[DisplayMTKView drawRect:] DisplayMTKView.m:647 (CDTest:x86_64+0x1000eb774)
    #4 -[MTKView draw] <null>:11239984 (MetalKit:x86_64+0x134d9)
    #5 cvCallback DisplayCVLinkWrapper.m:228 (CDTest:x86_64+0x1001f2e1c)
    #6 CVDisplayLink::performIO(CVTimeStamp*) <null>:11239984 (CoreVideo:x86_64+0x35ce)

  Mutex M578848238724123704 is already destroyed.

  Thread T35 (tid=386576, running) is a GCD worker thread

  Thread T30 (tid=386513, running) created by main thread at:
    #0 pthread_create <null>:11240032 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x283ed)
    #1 CVDisplayLink::start() <null>:11240032 (CoreVideo:x86_64+0x268f)
    #2 -[DisplayItemController setupWindows] DisplayItemController.m:312 (CDTest:x86_64+0x100012b39)
    #3 -[DisplayItemController initWithController:withContext:] DisplayItemController.m:90 (CDTest:x86_64+0x10000d71d)
    #4 -[AppDelegate applicationDidFinishLaunching:] AppDelegate.m:229 (CDTest:x86_64+0x100208d87)
    #5 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ <null>:11240032 (CoreFoundation:x86_64h+0x9aedb)
    #6 start <null>:11240032 (libdyld.dylib:x86_64+0x1014)

SUMMARY: ThreadSanitizer: data race MonitorMTKView.m:225 in __32-[MonitorMTKView drawRect:]_block_invoke

Replies

It may be flagging the write to the semaphore variable (not changing the state of the semaphpre itself). Check how many time dispatch_semaphore_create is getting called and whether you're writing to that property more than once.

dispatch_semaphore_create is only called with the MTKView is instantiated, and nowhere else. Very odd, indeed.

It could also be that there's a bufffer being overwritten and stomping on the property's memory. Try running with "Malloc Guard Edges" and "Guard Malloc" enabled to check for this.

Hmm, I use the MTKViewDelegate method draw(in view: MTKView) for the drawing and do not use the MTKView's drawRect method directly. If you use the delegate, then you do not have to create the display link. MTKView implements the display link and drives the view's drawRect for you when myview.isPaused = false. Using the dispatch semaphore within the delegate method works as expected. ( in my project )


I do recall if you used the MTKView's drawRect and the delegate at the same time there are strange problems. Think the docs indicate not to do that.


I'd use the delegation since MTKView will then do more of the work for you.


Perhaps your display link is conflicting with MTKView's built in display link?


Hope that helps.

These MTKViews are setup to require manual draw calls (enableSetNeedsDisplay = NO and paused = YES on the view). This shuts off the internal display link calls. These views are driven by my own CVDisplayLink render loop.


I do this as the stock MTKView setup stutters when you mouseUp on that app's menus. Even with Apple's sample code.