由 xpc_connection_send_message_with_reply_sync 超时引发的崩溃

 原创    2023-09-01

iOS 的一些系统接口需要调用 xpc_connection_send_message_with_reply_sync 方法与其它进程通信并返回结果,在低性能设备上打开 APP 后如果有频繁的 xpc_reply_sync 调用可能会诱发超时卡死。

跟踪线上问题时发现一个少量的 Crash Case,错误描述为:

Termination Reason:<RBSTerminateContext| domain:10 code:0x8BADF00D explanation:scene-create watchdog transgression: application<com.kanchuan.app>:226 exhausted real (wall clock) time allowance of 13.54 seconds

Crash 线程堆栈部分如下:

Thread 0 Crashed:
0      libsystem_kernel.dylib        	_mach_msg_trap
1      libsystem_kernel.dylib        	_mach_msg
2      libdispatch.dylib             	__dispatch_mach_send_and_wait_for_reply
3      libdispatch.dylib             	_dispatch_mach_send_with_result_and_wait_for_reply$VARIANT$mp
4      libxpc.dylib                  	_xpc_connection_send_message_with_reply_sync
5      Foundation                    	___NSXPCCONNECTION_IS_WAITING_FOR_A_SYNCHRONOUS_REPLY__
6      Foundation                    	-[NSXPCConnection _sendInvocation:orArguments:count:methodSignature:selector:withProxy:]
7      Foundation                    	-[NSXPCConnection _sendSelector:withProxy:arg1:]
8      Foundation                    	__NSXPCDistantObjectSimpleMessageSend1
9      CoreLocation                  	_CLGetStatusBarIconState
10     CoreLocation                  	_CLClientIsLocationServicesEnabled
11     CoreLocation                  	_CLClientRetrieveAuthorizationStatus

对应的源码调用入口为:

[CLLocationManager locationServicesEnabled];

由于业务的特殊要求,之前曾对 CLLocationManager的弹窗问题 做过处理,在打开 APP 后会判断系统定位服务是否开启。就这么简单的一个调用竟然能引起线上一天十几个设备的 Crash。

从已知的信息上来,这又是一起典型的因 APP 卡死超时导致被 watchdog 关闭的 Crash。Crash 集中发生在 iPhone 6s、iPhone 7 等低端设备上,且大部分都是 iOS 15.7 系统,也有一些 iOS 16 系统,表现为在打开 APP 后即 Crash。问题似乎已经很明显了,当我准备复现问题时,发现就算找到了对应的设备和系统,再加上人为营造苛刻的测试环境,都始终无法复现问题。哎,这类问题看来只能到线上放量时才能暴露出来。

xpc_connection_send_message_with_reply_sync

XPC(XPC Services)是在 iOS 和 macOS 系统中用于进程间通信(IPC)的框架,用于在不同的应用程序或进程之间进行安全且高效的数据交换。开发者在上层应用调用的一些系统 API 都需要通过 libxpc.dylib 中的 xpc_connection_send_message_with_reply_sync 方法同步从系统对应的服务中获取结果并返回,比如 UIDevice.identifierForVendor、NSUserDefaults、UIApplication.openURL等,CLLocationManager.locationServicesEnabled 内部也通过该方法向系统定位服务查询结果。

对应的还有一个 xpc_connection_send_message_with_reply,这是一个异步方法,不会存在超时的问题。典型的,Runloop的 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__事件回调就是通过 xpc_connection_send_message_with_reply 发送 XPC 消息,用于处理 UI 事件。

问题出现的可能原因

由于始终无法复现问题,这里只能猜测原因:很可能是在 APP 启动时,在多线程环境中执行了太多需要触发 xpn reply sync 调用的方法(一个很不起眼的系统方法内部都可能调用了xpn reply sync),导致内部消息处理出现了问题,一直无法返回,直到被 watchdog 杀死。考虑到错误集中在 iOS 15.7 系统上,有理由怀疑是 iOS 15.7 引入的 bug。

如何修复?

  1. 检查系统启动时调用 xpc_connection_send_message_with_reply_sync 的频次,能后移的尽量后移;
  2. 尽量避免调用能触发 xpc_connection_send_message_with_reply_sync 的接口。

比如 kanchuan 遇到的这个 locationServicesEnabled 问题,在主线程调用时,可能会收到提醒:

This method can cause UI unresponsiveness if invoked on the main thread

推荐在 CLLocationManager 的 delegate 中监听定位权限的变化(代理是由系统回调的,不会存在 xpn reply sync 问题 ),当定位状态开启后再执行业务逻辑:

- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager {
    if (@available(iOS 14.0, *)) {
        CLAuthorizationStatus status = manager.authorizationStatus;
        if (status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse) {
        }
    } else {
        // Fallback on earlier versions
    }
}

- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
    if (status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse) {
    }
}

以上修改发布到新版本后,Crash 得到解决。

相关文章:

Swift并发编程 - 理解 async 和 await
iOS:清除Xcode缓存
iOS文件系统目录结构
Validating App Store Receipts
iOS NSAttributedString NSHTMLTextDocumentType陷阱

发表留言

您的电子邮箱地址不会被公开,必填项已用*标注。发布的留言可能不会立即公开展示,请耐心等待审核通过。