> My assumption is that you’ll need a memory barrier …
yes, looks like it. though i never saw it used for audio on ios/mac..
>> i assume this a current limitation (or a bug) of thread sanitizer and memmove doesn't actually do anything special (like flushing caches or putting memory fences) than the simple assignment does, correct?
> Correct.
i remember BlockMove was doing something special about caches :-)
> With regards priority inversion, most of the built-in locking primitives provide a priority boost to the thread holding the lock when there’s a high-priority thread waiting for it. You can learn more about this in the Lock Ownership section of WWDC 2017 Session 706 Modernizing Grand Central Dispatch Usage.
thanks, will definitely watch.
i created a simple test application that tests bullet point #2 statement (the necessity of coordinating accesses to buffer contents and its position). it is a 370 line all-in-one-source-file app -- no nib files -- for iOS and macOS. on iOS the app has some minimal UI, on macOS it is a console app that prints some info every second. the code is mainly “C” as far as audio is concerned, notable exception is std::atomic<int> used for ring buffer positions to avoid their data races. no attempt is done in code to coordinate buffer content and its position. the app works like this:
- the app gradually generates and fills the ring buffer with audio data (a chord of three notes). this happens in the main thread. the assumption is that in order for the issue to happen the ring buffer size shall be small enough and so shall be the filling chunks. i tested it with filling duration of down to 10ms at 48kHz (so 480 samples == 2K bytes). also with big filling chunks / ring buffer (e.g. 1/3 sec / 1 sec).
- in the IO callback (real time audio thread) the app reads from the ring buffer and plays it.
- to ensure the data is not damaged (the actual test of the bullet point #2 statement) the app also generates the relevant chunk of audio data “as it should be” on the fly and compares it with what came from the ring buffer - it asserts the two are equal, so in debug build the app will break.
- the main UI on iOS / or console output on mac shows various information (sample rate, etc) along with the “error count”. should this error count be anything but zero, ever - there is indeed a need to coordinate accesses of ring buffer contents and its position.
- if you want to compile this app i can either provide the project or just create a new project for iOS app and remove everything including the “Main storyboard file base name” reference in it’s plist. as for macOS target - use console app. i found i had to manually add frameworks (in the Xcode settings pane) when C++ is used in the app. also enable background audio in iOS target if you want the app to work in background.
- note that error checking in the app is minimal - the app asserts on errors.
i was not able to surface the problem #2 so far, tested debug and release builds on iPhone X, iPhone 6s, iPad Pro / MacBook Pro. will soon test the app on the most recent iPhone and update here if it is any different.
i foresee this awkward conversation should i talk about this app with DTS:
>>>>>>>>>>>>>>>
ME: please see the app, it works but it shall not.
DTS: we tested your app and we weren’t able reproduce any issues. do you know on what hardware and/or under what circumstances it shall misbehave?
ME: no, but it may fail when iPhones are ported to alpha CPU or when ARM moves to a weaker memory model or when tools are evolved or maybe on the current hardware in other cases which i haven’t found - please help me finding those. Quinn told me that to play by the rules i shall synchronise the access of the buffer and its position otherwise i am (and always was) in trouble without realising it.
DTS: you shall talk to Quinn then. from what we see the app works ok and we are unable to find a case when it doesn’t work so no changes are needed. following the YAGNI principle, if the app breaks in the future - you fix it right there and then in the future. we also compared what you do in the app to other relevant sample code and found no significant differences. having said that we have to close this DTS incident.
<<<<<<<<<<<<<<
let’s see what they actually say.
==== the app ====
475 lines skipped, probably too big for this forum. will send it to those who want it.
the most interesting fragment looks like this:
static OSStatus renderCallback(void* refCon, AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* ts, UInt32 element, UInt32 numFrames, AudioBufferList* ioData) {
..
ringRead(data, size);
generateAudio(&phase, data2, size);
assert(memcmp(data, data2, size) == 0);
..
}