CallKit requestTransaction error code 2

Hello,

In production, a large number of users experience outgoing call reporting fails with the following error:

com.apple.CallKit.error.requesttransaction Code=2

The iOS version doesn't matter, errors are present in v15-26

Details

  • My CXProvider held as a global singleton, so it’s unlikely to be deinited.
  • There is no explicit call to CXProvider.invalidate() in the app.

If I manually invalidate the CXProvider, I observe the expected failure when trying to create an outgoing call (com.apple.CallKit.error.requesttransaction error 2).

However, If I recreate the CXProvider after the error, outgoing calls are reported correctly.

Many users trigger the providerDidReset delegate method (CXProviderDelegate) before this error.

According to the documentation, providerDidReset can be called by the system, and we are supposed to end all active calls, but the documentation doesn't suggest recreating the CXProvider.

Question

Should I recreate CXProvider after providerDidReset and forget about that, or could this error be caused by something else?

In production, a large number of users experience outgoing call reporting fails with the following error:

How many is a "large number"? The problem here is that what trigger this:

Many users trigger the providerDidReset delegate method (CXProviderDelegate) before this error.

...is an XPC connection failure with callservicesd, typically caused by callservicesd crashing. That's not common, so if your app is causing "frequent" resets, then that's an issue that might be worth looking into.

Moving to here:

com.apple.CallKit.error.requesttransaction Code=2

Error 2 is "CXErrorCodeRequestTransactionErrorUnknownCallProvider", which basically means callservicesd doesn't recognize your provider as "valid". Again, that's not a common failure, but it could happen if your existing provider didn't register properly if/when it reconnected.

That leads to here:

Should I recreate CXProvider after providerDidReset and forget about that, or could this error be caused by something else?

Yes, that's what I would do.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Dear Apple with regards to above - can you pls shed a light on connection between:

  • CXProvider delegate method providerDidReset
  • invalidate() method CXProvider

Questions (assuming provider and delegate are still alive and connected):

  1. if to call invalidate() then delegate gonna receive providerDidReset. Is it 100% always true?

  2. My current Xcode has line like: The provider must be invalidated before it is deallocated. So if system calls providerDidReset there is no need to call invalidate() on existing provider. Just deallocate existing and create a new one?

Dear Apple, with regards to the above - can you please shed light on the connection between:

CXProvider delegate method providerDidReset

This is called when the XPC connection between CXProvider and callservicesd is interrupted.

invalidate() method in CXProvider

Among other things, this breaks the XPC connection between CXProvider and callservicesd.

  1. If you call invalidate(), then the delegate will receive providerDidReset. Is it 100% always true?

When it comes to software engineering, I am violently allergic to the word "always". The system is big, complicated, and constantly evolving, which means there's a pretty big difference between:

  • "This is what the system normally does"

VS

  • "The system behaves this way under all circumstances today, in previous versions, and in all future versions"

Yes, I suspect providerDidReset generally does get called when you invalidate, both now and in the past. That's the strongest promise I'll make.

  1. My current Xcode has a line like: The provider must be invalidated before it is deallocated.

Yes, that's what the comment in the CXProvider.h says.

So if the system calls providerDidReset, there is no need to call invalidate() on the existing provider. Just deallocate the existing one and create a new one?

No. The header says "The provider must be invalidated before it is deallocated". That means you need to call "invalidate" before you drop your last reference to a CXProvider.

Note that the issue here isn't actually about what invalidate() does today or even whether that call is necessary. The issue here is about complying with the API contract. We told you that you MUST call invalidate before CXProvider will be deallocated. That means we're allowed to leak that CXProvider object if you fail to call invalidate on it. The fact that we may not happen to leak today doesn't mean we didn't leak in the past or that we won't leak in the future.

That leads to my question- what problem are you actually having? Why is it an issue to call invalidate?

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

CallKit requestTransaction error code 2
 
 
Q