Frequent providerDidReset Callbacks in Production

Hello,

We're seeing a high rate of providerDidReset callbacks in production across a large user base (iOS 16, 17, 18, and 26). I'd like to understand both the correct way to handle this delegate method and strategies to reduce its frequency.

Background

  • The callback occurs across all iOS versions we support and is not isolated to a specific device or region.
  • The callback can occur in any app state (foreground, background, inactive), however it is most dominant in the background state — particularly during VoIP push notification handling.
  • The callback is more prevalent during long app sessions — for example, when the app has been running continuously for a day or overnight.
  • We do not call CXProvider.invalidate() anywhere in our codebase explicitly.
  • After providerDidReset fires, subsequent transactions fail with CXErrorCodeRequestTransactionErrorUnknownCallUUID (error code 4).

Re-initializing the provider via initializeProvider() resolves this error.

Our Implementation

We use a singleton proxy class (CallKitProxy) that owns the CXProvider. Below is a simplified version — some logging and non-essential parts have been removed for brevity.

@objcMembers
public final class CallKitProxy: NSObject {
    private var cxProvider: CXProvider?
    private let cxCallController: CXCallController
    private let cxCallObserver: CXCallObserver

    private override init() {
        cxCallObserver = CXCallObserver()
        cxCallController = CXCallController()
        super.init()
        initializeProvider()
        cxCallObserver.setDelegate(self, queue: nil)
    }

    private func initializeProvider() {
        let configuration = providerConfiguration()
        cxProvider = CXProvider(configuration: configuration)
        cxProvider?.setDelegate(self, queue: nil)
    }

    private func providerConfiguration() -> CXProviderConfiguration {
        let soundName = SharedUDHelper.shared.string(forKey: .pushNotificationSoundNameForCall)
        let sound = CallNotificationSounds(name: soundName ?? "ringtoneDefault")

        let configuration = CXProviderConfiguration()
        configuration.supportsVideo = true
        configuration.maximumCallsPerCallGroup = 1
        configuration.maximumCallGroups = 1
        configuration.supportedHandleTypes = [.phoneNumber, .generic]
        configuration.iconTemplateImageData = UIImage(
            named: "callkit_mask",
            in: .main,
            compatibleWith: nil
        )?.pngData()
        configuration.ringtoneSound = sound.name

        return configuration
    }

    public func requestTransaction(
        action: CXCallAction,
        completion: @escaping (Error?) -> Void
    ) {
        let transaction = CXTransaction(action: action)
        cxCallController.request(transaction) { error in
            completion(error)
        }
    }
}

extension CallKitProxy: CXProviderDelegate {
    public func providerDidReset(_ provider: CXProvider) {
        // End any active calls, then re-initialize the provider
        initializeProvider()
    }
}

Questions

1. Is re-initializing the provider inside providerDidReset the correct approach?

The documentation states that providerDidReset signals the provider has been reset and all calls should be considered terminated. Should we be calling CXProvider.invalidate() on the old instance before creating a new one? Or is assigning a new CXProvider to cxProvider (which releases the old instance) sufficient?

2. What could be causing providerDidReset to fire so frequently, and how can we reduce it?

We're particularly concerned about cases triggered during VoIP push handling in the background and inactive states. Are there known conditions — such as provider configuration changes, app lifecycle events, or system memory pressure — that commonly trigger this callback? And are there any recommended patterns to make the provider more resilient in these scenarios?

Thank you.

We're seeing a high rate of providerDidReset callbacks in production across a large user base (iOS 16, 17, 18, and 26).

How frequent is "frequent"?

The documentation states that providerDidReset signals the provider has been reset and all calls should be considered terminated. Should we be calling CXProvider.invalidate() on the old instance before creating a new one? Or is assigning a new CXProvider to cxProvider (which releases the old instance) sufficient?

My recommendation would be that you destroy all existing call infrastructure (both CXProvider and all other CallKit crashes), call invalidate as well, then recreate a new CXProvider and recreate all other call infrastructure.

Having said that, the EXACT details of that process generally don't matter all that much. All existing CallKit objects will be non-functional; most VoIP apps don't persist long enough that issues like small amounts of leaked memory actually become relevant.

  1. What could be causing providerDidReset to fire so frequently, and how can we reduce it?

providerDidReset happens because callservicesd has terminated and relaunched. Typically, that's because of issues totally outside of your app’s control.

Note that this behavior is actually a side effect of that:

We're particularly concerned about cases triggered during VoIP push handling in the background and inactive states.

...as your app didn't actually have anything to do with the termination.

Are there known conditions — such as provider configuration changes, app lifecycle events, or system memory pressure — that commonly trigger this callback?

The circumstances actually vary considerably between configurations and over time. This is really a case of the delegate pattern making very different issues appear "the same", not a single failure.

And are there any recommended patterns to make the provider more resilient in these scenarios?

The main recommendation is to properly isolate your CallKit stack such that you can create and destroy it "at will" with minimal disruption to the rest of your app.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Frequent providerDidReset Callbacks in Production
 
 
Q