FRONTBOARD crash App killed while running in the background.

PLATFORM AND VERSION iOS Development environment: Xcode 16.2, macOS 15.5 Run-time configuration: iOS 18

DESCRIPTION OF PROBLEM Our app (a VoIP and messaging app) has been experiencing a crash when running in the background for long periods of time (a couple of days) while receiving calls, and message notifications. If the app is not receiving notifications, we don't get any crashes while it runs in the background.

It is worth mentioning that we have several pushes that are background pushes and they could happen depending on the outcome of an incoming call. We have the two pushes:

  • incoming call bye: let the app know that the calling end hanged up the call.
  • incoming call answered: lets the app know that another device (with the same shared number) answered the call (web app, Android).

Those pushes are delivered within 30 seconds after the call starts. I assume that since the app was awakened by a VoIP push, those background notification won't count towards the iOS restriction of not getting too many background pushes: "The number of background notifications allowed by the system depends on current conditions, but don’t try to send more than two or three per hour."

Let me know if the above assumption is not accurate.

I don't see details in the crash report (such as a "Termination Description") that could guide me to address the issue. So I would appreciate if you can give me some insight on what could be causing this.

Here is part of the crash report: Exception Type: EXC_CRASH (SIGKILL) Exception Codes: 0x0000000000000000, 0x0000000000000000 Termination Reason: FRONTBOARD 0xbaadca11 <RBSTerminateContext| domain:10 code:0xBAADCA11 explanation:<no explanation given> reportType:CrashLog maxTerminationResistance:Interactive>

Triggered by Thread: 0

Thread 0 name: Thread 0 Crashed: 0 libsystem_kernel.dylib 0x00000001dda93ce4 mach_msg2_trap + 8 1 libsystem_kernel.dylib 0x00000001dda9739c mach_msg2_internal + 76 (mach_msg.c:201) 2 libsystem_kernel.dylib 0x00000001dda972b8 mach_msg_overwrite + 428 (mach_msg.c:0) 3 libsystem_kernel.dylib 0x00000001dda97100 mach_msg + 24 (mach_msg.c:323) 4 CoreFoundation 0x000000018c886900 __CFRunLoopServiceMachPort + 160 (CFRunLoop.c:2637) 5 CoreFoundation 0x000000018c8851f0 __CFRunLoopRun + 1208 (CFRunLoop.c:3021) 6 CoreFoundation 0x000000018c886c3c CFRunLoopRunSpecific + 572 (CFRunLoop.c:3434) 7 GraphicsServices 0x00000001d9a65454 GSEventRunModal + 168 (GSEvent.c:2196) 8 UIKitCore 0x000000018f299274 -[UIApplication run] + 816 (UIApplication.m:3845) 9 UIKitCore 0x000000018f264a28 UIApplicationMain + 336 (UIApplication.m:5540) 10 SwiftUI 0x00000001913a97a4 closure #1 in KitRendererCommon(:) + 168 (UIKitApp.swift:68) 11 SwiftUI 0x00000001910af01c runApp<A>(_:) + 112 (UIKitApp.swift:16) 12 SwiftUI 0x00000001910aeed0 static App.main() + 180 (App.swift:136) 13 TheApp Business 0x0000000100686028 static TheApp_BusinessApp.$main() + 52 (TheApp_Business.swift:0) 14 TheApp Business 0x0000000100686028 main + 64 15 dyld 0x00000001b375bf08 start + 6040 (dyldMain.cpp:1450)

STEPS TO REPRODUCE

  • Open the app.
  • Leave the app running in the background while it is receiving notifications (VoIP or messages).
  • Bring the app to the foreground after a day or two of it running in the background.
  • Notice that after opening the app, the launch screen is presented.

I don't see details in the crash report (such as a "Termination Description") that could guide me to address the issue. So I would appreciate it if you can give me some insight on what could be causing this.

The key detail here is:

Termination Reason: FRONTBOARD 0xbaadca11

Which means:

0xbaadca11 (3131951633) — pronounced "bad call"

The operating system terminated the app for failing to report a CallKit call in response to a PushKit notification.

I assume that since the app was awakened by a VoIP push, those background notifications won't count towards the iOS restriction of not getting too many background pushes:

Correct. There isn't any restriction or limitation on the number of VoIP pushes that can be sent to an app and, indeed, the main reason the CallKit reporting requirements were introduced is that VoIP pushes were being widely abused and we didn't want to artificially limit push volume.

However...

Those pushes are delivered within 30 seconds after the call starts.

...I think using PushKit for things like this is a mistake:

incoming call bye: let the app know that the calling end hanged up the call. incoming call answered: lets the app know that another device (with the same shared number) answered the call (web app, Android).

The problem here is that you MUST report a new call to CallKit for every push you receive so if the user ended the call before either of those messages, you're going to be forced to present an additional, unnecessary, incoming call screen.

IF you choose to use this approach, then the correct way to handle this is to report a new incoming call using the same call ID as your existing call. What will then happen is one of two things:

  1. Best case, the existing call is still active, so your "extra" call report will fail with a duplicate call ID error. You can ignore that error and don't need to take any additional action.

  2. Worst case, the existing call has already ended, so the call UI will trigger again. Your app (or the user) will then need to end the call.

Returning to the crash itself:

Our app (a VoIP and messaging app) has been experiencing a crash when running in the background for long periods of time (a couple of days) while receiving calls and message notifications.

Based on my own experience, I suspect what's actually happening is something like this:

  1. Your app is sent to the background.

  2. At some point, the system terminates your app. The exact reason doesn't matter, as it's normal and expected for VoIP apps to be terminated when they're in the background.

  3. A VoIP push arrives, and callservicesd (the daemon that manages VoIP apps) launches your app into the background so that it can deliver that push.

  4. Your app did not initialize itself properly and, in particular, it never created a PKPushRegistry object and/or set up the delegate.

  5. Because PKPushRegistry was never created, callservicesd is unable to deliver the push into your app. A short time after app launch (~7s), callservicesd "gives up" and terminates your app, setting the exception code 0xbaadca11 to indicate why you've been terminated.

A few supporting details for #5:

  • If you look at the launch and crash times in your crash logs, I believe you'll find that it's ~7s. That happens to be the current delay callservicesd uses for its enforcement mechanism. There are cases where it could be longer (because something else launched you into the background at an earlier point), but ~7s is what I see most of the time.

  • If you look at the stack trace you sent above, it shows your app as idle. That's typical of the pattern above, as that 7s is generally enough time that most apps complete their own launch cycle.

Exactly while failure #4 occurs tends to be very specific to the app implementation; however, the two most common cases I see are:

  1. The app assumes the user will always log in when the app launches, which breaks when the app is launched into the background.

  2. The app assumes data will be available when it isn't, particularly in the case where the app is relaunched on a locked device immediately after reboot ("Prior to first unlock").

Note that those are the two common, "basic" cases. The details of this can be much more complicated and involved (see this thread for an example). I'd also highlight my advice from this post on that thread:

"On that last point, my recommendation is that VoIP apps should create their basic call handling "infrastructure" as early as possible (typically, applicationDidFinishLaunching) AND that the "core" component should be able to FULLY process a call without ANY of the rest of your app functioning at all."

And then later:

"The goal of my recommendation above is to prevent exactly the kind of issue you're seeing here. It won't prevent your app from failing (obviously, there is some reason your app wasn't able to initialize the way it should have), but it does change the situation from "my app is dying and I don't know why" to "my app received a VoIP push and it couldn't complete the call because <insert component> didn't work correctly."

The crash you’re seeing simply cannot happen in ANY app that actually follows that advice.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks for your response, clarifying the reason the app is killed is very helpful in this case.

I think everything is clear to me at this point. I just want to clarify one thing about my report: the "incoming call bye" and "incoming call answered" pushes are not PushKit notifications, they are the usual background notifications with the content-available flag set to 1. This is why they were the initial suspects. Let me know if you advise against using these background pushes in this case, even if they aren’t to blame.

I think everything is clear to me at this point. I just want to clarify one thing about my report: the "incoming call bye" and "incoming call answered" pushes are not PushKit notifications, they are the usual background notifications with the content-available flag set to 1. This is why they were the initial suspects. Let me know if you advise against using these background pushes in this case, even if they aren’t to blame.

That's an unusual use for those pushes, but no, there's nothing wrong with using them for that. More specifically, as long as these are standard pushes that are delivered into your app through the standard notification delegates, NOT through PushKit, then they cannot cause the crash you're seeing. You may have reliability issues (because the push simply doesn't reach your app), but you won't crash.

Also, just to make sure this was clear, the ONLY thing that can cause this crash:

Termination Reason: FRONTBOARD 0xbaadca11

...is failing to report a CallKit call in response to a VoIP push. Similarly, the dynamics described in this post mean that the ways you trigger this termination are:

  1. (Common case) Issues in your app’s logic mean that you don't have a PKPushRegistry object/delegate, making it impossible for the system to deliver the call notification to your app.

  2. (Uncommon case) You've intentionally disabled the in-process mechanism which would normally have caused your app to crash when it returned from the registry delegate without reporting a call.

I want to highlight this because I've seen developers waste a lot of time looking for more complex or elaborate explanations, when the underlying issue was that their app simply didn't work the way that it did/should, so they were hitting #1.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

FRONTBOARD crash App killed while running in the background.
 
 
Q