[iOS 26][SDK 26] Application never received providerDidBegin(_:) delegate

Observed few times that providerDidBegin(_:) delegate never called for the complete app session after app init(as part of this CXProvider registered) which was built with SDK 26 and running on iOS 26.

This issue observed multiple times with our testing. Since there is no providerDidBegin:, client is marking CallKit as not ready and never report any calls for VoIP APNS and ended up in app crash due to "[PKPushRegistry _terminateAppIfThereAreUnhandledVoIPPushes]"

Please refer for sysdiagnose logs : FB19778306

Observed a few times that providerDidBegin(_:) delegate never called for the complete app session after app init(as part of this CXProvider registered) which was built with SDK 26 and running on iOS 26.

Yes, it looks like there is a bug there. More specifically, calling CXProvider.init(configuration:) kicks off an XPC connection to callservicesd on a secondary thread, the end of which eventually calls providerDidBegin(_:). If that completes before you're able to call setDelegate, then you'll see the problem you're describing.

Oddly, as far as I can tell, the issue has basically been present from the beginning, so I'm not sure why I haven't heard of this before. Are you creating CXProvider on a background thread? I generally recommend using the main thread for CallKit and PushKit, and it's possible that initializing them on a background thread might make this problem more likely due to differences in thread priority.

Moving to here:

This issue was observed multiple times with our testing. Since there is no providerDidBegin:, the client is marking CallKit as not ready and never reports any calls for VoIP APNS and ended up in an app crash due to "[PKPushRegistry _terminateAppIfThereAreUnhandledVoIPPushes]"

Don't do this. As you noted, failing to report a call will crash your app, so skipping this is just going to get your app killed. The most likely reason you missed providerDidBegin(_:) is the timing issue above, but even if there is some delay, I think you'll still be better off reporting the call.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks for the update.

Oddly, as far as I can tell, the issue has basically been present from the beginning, so I'm not sure why I haven't heard of this before. Are you creating CXProvider on a background thread? I generally recommend using the main thread for CallKit and PushKit, and it's possible that initializing them on a background thread might make this problem more likely due to differences in thread priority.

Here as mentioned in the feedback, the CallKit gets initialised is in Main queue, please refer the below code:

        callkitProvider = [[CXProvider alloc] initWithConfiguration:config];
        [callkitProvider setDelegate:self queue:dispatch_get_main_queue()];

But client has getting initiated in secondary queue for the PushKit,

                voipRegistry = [[PKPushRegistry alloc] initWithQueue: voipPushNotificationQueue];

does this could lead to the issue regarding not received providerDidBegin(_:) delegate ?

Here as mentioned in the feedback, the CallKit gets initialised is in Main queue, please refer the below code:

That's the queue you're targeting, but is that code also running on the main queue?

But client has getting initiated in secondary queue for the PushKit, does this could lead to the issue regarding not received providerDidBegin(_:) delegate ?

Yes, I think that's likely a significant factor. I'm not sure why you're using a background for this, but my advice has long been to use the main thread for both of these delegates. In both cases, the delegate should be doing minimal work that does not block, so neither should disrupt the main thread.

However, the other issue here also here:

Since there is no providerDidBegin:, client is marking CallKit as not ready

Expanding on what I said earlier, I think the simplest answer here is to simply ignore providerDidBegin and treat the CXProvider as fully functional from the moment it's created.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Yes as mentioned earlier, CallKit gets initialised is in Main queue (the mentioned code init is happening in main queue)

That's the queue you're targeting, but is that code also running on the main queue?

As you suggested,we have now initialised PushKit on the main queue. We are currently monitoring the issue since it is not consistently reproducible.

Since the issue is not consistent we are monitoring this issue.

I'm still not entirely convinced that initialising PushKit on a secondary queue might led the root cause of this problem.

As per Feedback analysis:

The user launched the application at

[07/31 15:04:45:858]*******{}BEGIN LOGGING{}*****

Here user has put app to background

[07/31 15:20:41:695][ 0x105147e80]<ALA_SIGNAL>: [OS-PLT] -[AppDelegate applicationDidEnterBackground:]

and then received an VoIP APNS and it got crashed,

[07/31 15:20:55:639][ 0x122c53680]<PushToTalk> [Pushnotif] [] <ALA_SIGNAL>: [OS-CCF] Enter -[PushNotificationManager pushRegistry:didReceiveIncomingPushWithPayload:forType:withCompletionHandler:]

and did not receive a callback for approximately 15 minutes of the entire app session.

so does really initialising PushKit on a secondary queue is the primary concern for this issue?

and did not receive a callback for approximately 15 minutes of the entire app session.

What callback are you referring to here? providerDidBegin(_:)?

Jumping back to what I said here:

Yes, it looks like there is a bug there. More specifically, calling CXProvider.init(configuration:) kicks off an XPC connection to callservicesd on a secondary thread, the end of which eventually calls providerDidBegin(_:). If that completes before you're able to call setDelegate, then you'll see the problem you're describing.

The problem that's actually happening here is that the system actually DID try to call "providerDidBegin()", but was unable to do so because the delegate had not been set "yet". In other words, there is an inherent race condition between:

  1. The backend logic which "CXProvider.init(configuration:)" kicks off completing (which is what triggers "providerDidBegin()").

AND

  1. Your app calling CXProvider.setDelegate() so that the system CAN call your "providerDidBegin()".

If #1 completes before #2, then providerDidBegin() will never be called. One obvious note on all this- the FIRST thing your app should do after calling CXProvider.init(configuration:) is call CXProvider.setDelegate(). Any work that occurs between those two points will greatly increase the likelihood of failure.

so does really initialising PushKit on a secondary queue is the primary concern for this issue?

To be clear, my comment about PushKit and threading was entirely speculative. I don't know what's actually causing the race to occur, but I do suspect that some amount of thread complexity is required. Putting that another way, I don't think a single threaded app that did this on the main thread could trigger hit the race condition above:

... CXProvider.init(configuration:) ... CXProvider.setDelegate(...)

That's not because it actually eliminates the race condition (it doesn't), but is because the combination of:

  • The high scheduling priority of the main thread.

  • Lack of contention from other threads in the same process.

...would make it VERY hard for #1 to finish "first".

However, ALL of this isn't really the solution. The actual solution here is:

Expanding on what I said earlier, I think the simplest answer here is to simply ignore providerDidBegin and treat the CXProvider as fully functional from the moment it's created.

Expanding on that, there isn't really any reason for your app to delay anything based on providerDidBegin. Just start using the provider and everything will work fine.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Expanding on that, there isn't really any reason for your app to delay anything based on providerDidBegin. Just start using the provider and everything will work fine.

Without this check in one of the earlier case CallKit session went into a bad state and there was no recovery for the CallKit session until swipe kill(we don't have the logs now for the instance).

Anyway, removed this check and proceeding to report to CallKit without providerDidBegin(_:) check. Will update here if any issues observed. Thanks

As you suggested,we have now initialised PushKit on the main queue. We are currently monitoring the issue since it is not consistently reproducible.

Note that my concern here isn't really about using threads, it's about creating unnecessary race conditions. In concrete terms, it's common for a PushKit delegate to assume its CXProvider target exists (so it can call reportNewIncomingCallWithUUID), but if initialization is happening on a different thread, it could be possible for the PushKit initialization to "get ahead", so the PushKit delegate fires before you've finished initializing CallKit. You can eliminate any possibility of that by following some simple guidelines:

  • Use the same thread for CallKit and PushKit. I normally recommend the main thread, but that doesn't actually matter.

  • Set up CallKit and PushKit on the same thread you'll use for the delegate and initialize all of them at the same time. This ensures that none of your delegates can fire until everything has been created.

  • Set up CallKit first, then PushKit.

Finally, I’d give the core advice I've given on other threads:

"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. For example, it should not assume that you have a connection to your server or that your network infrastructure even EXISTS."

A very large percentage of VoIP issues I look at ultimately boil down to "something odd/unexpected happened, so my app didn't call reportNewIncomingCallWithUUID". The solution to that is really simple- structure your code so that you ALWAYS call reportNewIncomingCallWithUUID.

Anyway, removed this check and proceeding to report to CallKit without providerDidBegin(_:) check. Will update here if any issues observed. Thanks

Sounds good. In terms of testing CallKit's behavior and validating that you're unlikely to have problems, you can try this sequence:

callkitProvider = [[CXProvider alloc] initWithConfiguration:config];
[callkitProvider setDelegate:self queue:dispatch_get_main_queue()];
[callkitProvider reportNewIncomingCallWithUUID:....];

That's basically the "worst case" in terms of pushing commands into CallKit "early", but if you test it I think you'll find that it all works fine.

Please let me know if you run into any problems or have any questions.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

[iOS 26][SDK 26] Application never received providerDidBegin(_:) delegate
 
 
Q