Suppressed Assertion from Metal: A Crash Review
Background
Recently, I published the v1.14.0 release of PhotonCam, introducing some key features like redesigned histogram, HDR edit, and more. I have tested the app for about two weeks in TestFlight. It’s pretty stable, and no crash report was found by me or by the test users.
However, when the new version was finally released, some users reported that the app crashed right after it launched. I immediately reverted back to the previous version by publishing a new version built with the old code and started investigating the root issue.
The issue was “pretty” easy to reproduce: once I installed the app from the App Store and turned on the new Histogram feature, it immediately crashed. If the users have installed the old version with the Histogram feature on and upgrade to the new one, it will immediately crash.
However, installing the Debug or TestFlight version won’t crash in any kind of operation. It’s not an issue regarding the upgrade of the app; it’s literally an issue of the App Store version.
Crash log analysis
I first received some crash logs via the 3rd party Crash report, and the call stack of the crash looks like this:
Analyze this crash log:
It crashed right after the
addCompletedHandleris called fromMTLCommandBuffer.
The direct instance to trigger this crash is
MTLReportFailure.
It’s actually being killed by failing to pass an assertion.
That’s weird. I have tested so many times that this assertion has never been seen. Also, as an assertion, should it be valid when the app is in the Debug build?
Additionally, what’s the assertion that is failed to pass? Since no more information can be found in the crash log, I turned to use the Console app to find the logs from the Crash Report.
The crash is unable to reproduce in Debug mode, but there are always logs that can be found when your iPhone is connected to the Mac with the Console app opened.
And yes, the Crash Report gave me the assertion that failed to pass:
Failed assertion `Completed handler provided after commit call'
For now, it’s pretty clear that it’s due to the following code:
guard let commandBuffer = commandQueue.makeCommandBuffer() else {
return
}
// ...Encode commands into the command buffer
commandBuffer.commit()
commandBuffer.addCompletedHandler { // … }I mistakenly placed addCompletedHandler after the commit call, in which it has an assertion to make sure the addCompletedHandler should be called before the commit call. Or, block the thread to wait for the command buffer to finish.
And the Apple documentation about this method makes it clear that:
You can only call this method before calling the command buffer’s commit() method.
It was my mistake. But why does this assertion happen on the App Store version, while the Debug and TestFlight versions are safe from it?
Make the assertion work in Debug or TestFlight Release
You write a bug, and you learn it, and then fix it. But to prevent the accident from happening in the future, I need to figure out why the assertion can’t work in the Debug or TestFlight Release.
After some trying, it turns out to be another issue I made:
I turned the
MetalCaptureEnabledflag on in both Debug and Release builds by mistake.
When this setting is on, you can capture a Metal workload programmatically, as described in this documentation.
So here is the phenomenon about how this MetalCaptureEnabled flag affects the assertion:
As long as
MetalCaptureEnabledis set to true, the assertion will always be suppressed, and the code continues to work as expected.
The assertion will work when:
MetalCaptureEnabledis off, and the Debug Executable is off in the build scheme.
The assertion won’t work when Debug Executable is on in the build scheme.
In practice, I should set the MetalCaptureEnabled to true for Debug builds only. And if there are any API mistakes I use, the assertion should work in the TestFlight release build.
And here is the final question: why does the assertion work on the App Store version? Based on the experiment, it seems that the App Store version will somehow ignore the MetalCaptureEnabled, and the code assertion still exists in the release build and relies on some conditions in runtime to decide whether to run.
Conclusions
Here are a few things you should learn in this article:
Add a completion handler before committing a command buffer.
Be careful about the build settings you altered, and usually some settings should be on for Debug builds only.
Crash Report process in the OS can give more useful information, which may not be found in the call stack of the crash thread.
App Store versions are not always the same as the TestFlight versions. Be sure to check the App Store.
Thanks for reading this article!



