We have recently encountered an App crash, as shown in the picture.
We call this function as:
let session = AVAudioSession.sharedInstance() guard session.currentRoute.outputs.isEmpty == false else { return false }
The TestFlight caught this issue, and the iOS device information is attached:
Do you have any suggestions to avoid this crash?
Please check the crash files as attached.
So, this is a great example of why I always ask for a crash log, as the problem here doesn't actually have anything to do with the audio system itself. The problem is that your app is stuck in an infinite loop. For future reference, the most direct proof of this is that the number of stack frames on the crashing thread is very high (860) and that the "base" of the stack ISN'T a valid entry point for a thread. For the main thread, this would be "main", but other threads would be thread_start or a similar entry point in libpthread. Crash collection system artificially limits the number of frames it collects (otherwise you'd potentially have 1000s+ of lines of repeating frames), so you aren't actually "getting" the true "bottom" of the stack.
858 <...> closure #1 in DeviceManager.init() + 80
859 <...> partial apply for closure #1 in DeviceManager.init() + 24 (/:0)
860 <...> closure #1 in NSNotificationCenter.addObserver</a><a>(forType:object:queue:using:) + 232 (GaiaNotifications.swift:65)
In any case, the way I like to identify these looping cycles is:
- Scroll down into the "middle" of the stack list, so that the pattern is well established.
- Cut a large chunk of the stack "out" of the crash log, so that I know I'm collecting multiple loop cycles.
- Do a "find" for a particular function I see repeating. Note that you're trapped in a loop, so which function you pick in the cycle doesn't really matter.
- Pick two instances that are "next" to each other in the find, then delete everything before/after them.
- What's left is the cycle in your app that's creating the loop.
In your case, I searched for
0x0000000104847460 GaiaManager.didDiscoverDevice(_:) + 24 (/:259)
Which, edited down to remove some of the "noise", leaves you with this:
801 <...> partial apply for closure #1 in DeviceManager.init() + 24 (/:0)
802 <...> closure #1 in NSNotificationCenter.addObserver</a><a>(forType:object:queue:using:) + 232 (GaiaNotifications.swift:65)
...
808 <...> NSNotificationCenter.post</a><a>(_:) + 164 (GaiaNotifications.swift:51)
809 <...> specialized GaiaManager.didDiscoverDevice(_:) + 24 (GaiaManager.swift:263)
810 <...> GaiaManager.didDiscoverDevice(_:) + 24 (/:259)
...<...>
823 <...> GaiaConnectionFactory.startScanning(_:_:) + 12 (GaiaManager.swift:93)
824 <...> GaiaManager.startScanning(legacyIDs:type:) + 12 (/:0)
825 <...> DeviceManager.startScanningNewDevice() + 732
...
830 <...> partial apply for closure #1 in DeviceManager.init() + 24 (/:0)
831 <...> closure #1 in NSNotificationCenter.addObserver</a><a>(forType:object:queue:using:) + 232 (GaiaNotifications.swift:65)
...
837 <...> NSNotificationCenter.post</a><a>(_:) + 164 (GaiaNotifications.swift:51)
838 <...> specialized GaiaManager.didDiscoverDevice(_:) + 24 (GaiaManager.swift:263)
839 <...> GaiaManager.didDiscoverDevice(_:) + 24 (/:259)
Finally, anticipating a question, earlier I said:
...so the problem here doesn't actually have anything to do with the audio system itself.
That's true, even though all of your crash logs clearly reference the audio system:
Thread 0 name:
Thread 0 Crashed:
0 libsystem_pthread.dylib 0x00000001f5d2c118 ___chkstk_darwin (in libsystem_pthread.dylib) + 60
1 libdispatch.dylib 0x00000001a0d98aa4 _dispatch_mach_send_and_wait_for_reply (in libdispatch.dylib) + 428
...
8 AudioSession 0x00000001b5fbffc4 GetPropertyXPC(std::__1::shared_ptr<as::client::XPCConnection>, unsigned int, NSString*, bool) (in AudioSession) + 176
9 AudioSession 0x00000001b5fbfebc GetProperty(AVAudioSessionImpl*, NSString*, bool) (in AudioSession) + 92
Every thread has a fixed amount of memory ("the thread stack") which is used to store the data of every nested function call you make. Calling a function consumes a small amount of memory in that buffer ("pushing a frame onto the stack") and returning from that function returns that memory for use ("popping a frame off the stack"). The stack frame for every function has a specific size (determined at compile time) and every thread has a fixed amount of stack memory (set when the thread is created). The crash above then happens if you nest enough function calls that you exceed the threads’ available stack memory. Note this entry in the crash log:
VM Region Info: 0x16b6c7ff8 is in 0x16b6c4000-0x16b6c8000; bytes after start: 16376 bytes before end: 7
...
---> STACK GUARD 16b6c4000-16b6c8000 [ 16K] ---/rwx SM=NUL stack guard for thread 0
Stack 16b6c8000-16b7c4000 [ 1008K] rw-/rwx SM=SHM thread 0
Your main thread stack was 1008k-> 63 (16K) pages immediately followed by the "STACK GUARD", which is 1 (16K) page whose only purpose is to create this particular crash if you happen to overrun the stack. Note that together that's 64 pages total or exactly 1 MB, which is basically just a nice round number that is the default stack size for the main thread.
In any case, since both the stack size and the individual stack frames have a fixed size, you'll always end up crashing at the "same" exact point, assuming other factors (like changes between operating system versions) don't change the details of the stack frames involved. There's nothing particularly special about the point you actually crash, as it's simply the "moment" the stack got big enough to hit the stack guard.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware