ChangbaDevs / KTVHTTPCache

A powerful media cache framework.
MIT License
2.35k stars 435 forks source link

线程死锁,导致主线程等待并卡死的紧急Bug报告 #64

Closed irobbin1024 closed 6 years ago

irobbin1024 commented 6 years ago

线程死锁,导致主线程等待并卡死的紧急Bug报告

0x01 现象

我们有一个视频列表页面,用户滑动进行播放。并且支持预加载,就是会在当前视频播放的时候,提前下载好下个视频的1MB数据(跟此Bug应该没有关系)

用户进行视频播放,并滑动列表的时候,偶现卡死现象。表现为点击屏幕无反应。在debug状态,会导致崩溃,崩溃日志如下(详细的崩溃日志可查看附件):

{"app_name":"XiaoYing","timestamp":"2018-06-06 14:48:37.25 +0800","app_version":"7.0.0.180605","slice_uuid":"ccfbc542-1849-345b-abbf-adece47357ba","adam_id":0,"build_version":"856","bundleID":"com.quvideo.XiaoYing","share_with_app_devs":false,"is_first_party":false,"bug_type":"109","os_version":"iPhone OS 11.2.6 (15D100)","incident_id":"6F967100-F4B7-4D43-AD3D-F14D7204971D","name":"XiaoYing"}
Incident Identifier: 6F967100-F4B7-4D43-AD3D-F14D7204971D
CrashReporter Key:   c0186fb36a8c508ec3fca92fcfec956a86a13dec
Hardware Model:      iPhone6,2
Process:             XiaoYing [732]
Path:                /private/var/containers/Bundle/Application/34ADA1F7-CA8A-46EA-9BA0-24ADB5520FC8/XiaoYing.app/XiaoYing
Identifier:          com.quvideo.XiaoYing
Version:             856 (7.0.0.180605)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           com.quvideo.XiaoYing [574]

Date/Time:           2018-06-06 14:48:36.6438 +0800
Launch Time:         2018-06-06 14:18:33.2667 +0800
OS Version:          iPhone OS 11.2.6 (15D100)
Baseband Version:    8.30.01
Report Version:      104

Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d
Termination Description: SPRINGBOARD, scene-update watchdog transgression: com.quvideo.XiaoYing exhausted real (wall clock) time allowance of 10.00 seconds |  | ProcessVisibility: Foreground | ProcessState: Running | WatchdogEvent: scene-update | WatchdogVisibility: Foreground | WatchdogCPUStatistics: ( | "Elapsed total CPU time (seconds): 12.050 (user 12.050, system 0.000), 60% CPU", | "Elapsed application CPU time (seconds): 5.086, 25% CPU" | )
Triggered by Thread:  0

Filtered syslog:
None found

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib            0x0000000185330138 __psynch_mutexwait + 8
1   libsystem_pthread.dylib           0x0000000185447660 _pthread_mutex_lock_wait + 96
2   libsystem_pthread.dylib           0x00000001854475a4 _pthread_mutex_lock_slow$VARIANT$mp + 264
3   XiaoYing                          0x00000001010904e0 __41-[KTVHCDownload cleanBackgroundTaskAsync]_block_invoke + 6931680 (KTVHCDownload.m:223)
4   libdispatch.dylib                 0x000000018519aa14 _dispatch_client_callout + 16
5   libdispatch.dylib                 0x00000001851a2f08 _dispatch_continuation_pop$VARIANT$mp + 428
6   libdispatch.dylib                 0x00000001851ac848 _dispatch_source_invoke$VARIANT$mp + 1588
7   libdispatch.dylib                 0x00000001851a7570 _dispatch_main_queue_callback_4CF$VARIANT$mp + 720
8   CoreFoundation                    0x00000001857c3344 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
9   CoreFoundation                    0x00000001857c0f20 __CFRunLoopRun + 2012
10  CoreFoundation                    0x00000001856e0c58 CFRunLoopRunSpecific + 436
11  GraphicsServices                  0x000000018758cf84 GSEventRunModal + 100
12  UIKit                             0x000000018ee395c4 UIApplicationMain + 236
13  XiaoYing                          0x0000000100b4922c main + 1397292 (main.m:32)
14  libdyld.dylib                     0x000000018520056c start + 4

0x02 Bug分析

我们连接到Xcode进行了bug复线,在卡死的时候,点击暂停查看堆栈信息,可以看到主线程卡在如下代码:

- (void)cleanBackgroundTaskAsync
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self lock];
        if (self.delegateDictionary.count <= 0)
        {
            [self cleanBackgroundTask];
        }
        [self unlock];
    });
}

[self lock]的时候一直在忙等,导致主线程卡死。

经过我们的分析,发现原因如下:

线程62跟线程61之间有死锁现象。

下图是线程堆栈信息:

可以看到线程61的[KTVHCDataUnit workingRelease]调用了[KTVHCDataUnitPool unitShouldRearchive:] 方法,这两个方法都各自有自己的锁。

然后看线程62,[KTVHCDataUnitPool totalCacheLength] 调用了 [KTVHCDataUnit cacheLength]这两个方法都各自有自己的锁。

这里就比较明白了,当两个线程同时进入各自的第一个方法,KTVHCDataUnit KTVHCDataUnitPool 都会上锁,然后又进行了相互调用,因为已经被锁住了,这时候去申请锁,线程61和线程62会造成死锁。

下图表示线程62锁住KTVHCDataUnitPool

下图表示线程62锁住KTVHCDataUnit

下图表示线程61锁住KTVHCDataUnit

下图表示线程61锁住KTVHCDataUnitPool

那么线程61和线程62的死锁为什么会造成主线程死锁呢?

因为线程62是从KTVHCDownload类中的方法didReceiveResponse 开始调用,而didReceiveResponse 会讲KTVHCDownload锁住,然后在cleanBackgroundTaskAsync方法中的第啊吗会在主线程执行,并申请锁,这样,主线程也就卡住了。

0x03 解决

我们这边暂时还没找到合适的解决方法

libobjc commented 6 years ago

非常感谢如此详细的分析~~~

已发布 1.1.3 修复此问题。Pod 更新即可。

我这没复现环境,如有问题可继续与我联系。

donotlazy commented 6 years ago

我也遇到了类似的问题,版本是V1.1.5

donfishman commented 4 years ago

你好,我们最近也在做一个列表播放视频的功能,需要引用作者的这个库,实现视频缓存和列表预加载,但是我发现在引入这个库加载数据之后,会导致列表卡顿的现象,这个问题困扰我好久了,不知道您有没有遇到这样的问题,如果您有看到我的消息,还请回复我,非常感谢~ QQ:765658769 WX:try371202

libobjc commented 4 years ago

是最新的 2.0.1 版本吗?

LuCaiOS commented 2 years ago

你好,解决这个问题是修改的哪里?新到一个公司,里边用这个库比较老旧了,而且集成方式也是源码自己引入的,不知道现在的版本。我想参考一下哪里修改的来比对是否对我们公司项目是否是新的库。

LuCaiOS commented 2 years ago

我们这边现在遇到几个卡死也是因为递归锁多线程等待导致卡死的问题。 `1 libsystem_kernel.dylib ___psynch_mutexwait (in libsystem_kernel.dylib)

2 libsystem_pthread.dylib __pthread_mutex_firstfit_lock_wait (in libsystem_pthread.dylib)

3 libsystem_pthread.dylib __pthread_mutex_firstfit_lock_slow (in libsystem_pthread.dylib)

4 Foundation -[NSRecursiveLock lock] (in Foundation)

5 pregnancy -[KTVHCDataUnitPool lock] (in pregnancy:KTVHCDataUnitPool.m:285)

6 pregnancy -[KTVHCDataUnitPool archiveIfNeeded] (in pregnancy:KTVHCDataUnitPool.m:246)

7 CoreFoundation _CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER (in CoreFoundation)

8 CoreFoundation ____CFXRegistrationPost_block_invoke (in CoreFoundation)

9 CoreFoundation __CFXRegistrationPost (in CoreFoundation)

10 CoreFoundation __CFXNotificationPost (in CoreFoundation)

11 Foundation -[NSNotificationCenter postNotificationName:object:userInfo:] (in Foundation)

12 UIKitCore -[UIApplication _deactivateForReason:notify:] (in UIKitCore)

13 UIKitCore -[_UISceneLifecycleMultiplexer _performBlock:withApplicationOfDeactivationReasons:fromReasons:] (in UIKitCore)

14 UIKitCore -[_UISceneLifecycleMultiplexer _evalTransitionToSettings:fromSettings:forceExit:withTransitionStore:] (in UIKitCore)

15 UIKitCore -[_UISceneLifecycleMultiplexer uiScene:transitionedFromState:withTransitionContext:] (in UIKitCore)

16 UIKitCore ___186-[_UIWindowSceneFBSSceneTransitionContextDrivenLifecycleSettingsDiffAction _performActionsForUIScene:withUpdatedFBSScene:settingsDiff:fromSettings:transitionContext:lifecycleActionType:]_block_invoke (in UIKitCore)

17 UIKitCore +[BSAnimationSettings(UIKit) tryAnimatingWithSettings:actions:completion:] (in UIKitCore)

18 UIKitCore __UISceneSettingsDiffActionPerformChangesWithTransitionContext (in UIKitCore)

19 UIKitCore -[_UIWindowSceneFBSSceneTransitionContextDrivenLifecycleSettingsDiffAction _performActionsForUIScene:withUpdatedFBSScene:settingsDiff:fromSettings:transitionContext:lifecycleActionType:] (in UIKitCore)

20 UIKitCore __64-[UIScene scene:didUpdateWithDiff:transitionContext:completion:]_block_invoke.615 (in UIKitCore)

21 UIKitCore -[UIScene _emitSceneSettingsUpdateResponseForCompletion:afterSceneUpdateWork:] (in UIKitCore)

22 UIKitCore -[UIScene scene:didUpdateWithDiff:transitionContext:completion:] (in UIKitCore)

23 UIKitCore -[UIApplicationSceneClientAgent scene:handleEvent:withCompletion:] (in UIKitCore)

24 FrontBoardServices -[FBSScene updater:didUpdateSettings:withDiff:transitionContext:completion:] (in FrontBoardServices)

25 FrontBoardServices ___94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke_2 (in FrontBoardServices)

26 FrontBoardServices -[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] (in FrontBoardServices)

27 FrontBoardServices ___94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke (in FrontBoardServices)`