一个camera死锁问题的分析与解决

        最近处理一个bug,手机自带相机app和三方app全部无法打开相机。哪怕app退出再次进入也无法打开相机,只能重启手机了。

        打开log,发现如下出错信息:        

        04-14 14:53:55.629 1427 3487 E CameraService: CameraService::connect (PID -1) rejected (too many other clients connecting).

        结合下面的源码,可知是因为connect CameraService 的时候无法拿到mServiceLockWrapper 锁而导致CameraService一直无法连接成功。

​​​​​​​

 

        接下来继续查找使用mServiceLockWrapper 锁的函数,其中有个函数“evictClientIdByRemote” 也在使用这个锁。这个函数的作用是在app异常死掉后,CameraService做清理的,包括移除激活的client和关闭client打开的相机。那么会不会是这个函数拿到锁后未释放呢?

        非常幸运的是,在相机无法使用的情况下,测试人员尝试在微信中打开相机,微信出现了一次ANR, ANR log 中会有每个进程以及线程的堆栈信息。这好比是在一个案发现场,正好装有视频监控,这对破案作用太关键了。赶紧打开ANR文件搜索“evictClientIdByRemote”,果然有。

        可以确定的是“evictClientIdByRemote”函数拿到mServiceLockWrapper 锁后,被挂住,导致mServiceLockWrapper 锁未能释放。从上图的堆栈可以知道,Camera2ClientBase类的“disconnect”函数正挂在MutexLock函数中。

        继续撸代码,“disconnect”函数函数中只使用一个锁“mBinderSerializationLock”,搜代码发现这个锁,只在“disconnect”和“connect”两个函数中使用,继续在ANR文件中搜索这两个函数,果然找到了。

 

        从图中堆栈结合源码可以知道。另外一个线程中的“disconnect”函数已经获得“mBinderSerializationLock”,在IPC调用flush的时候没有返回。flush真正的处理是在camera_provider 进程中。先看一下camera_provider 进程中flush的堆栈信息,“OverrideFlush”函数在wait是被挂住。

         结合“OverrideFlush”函数的源码,可以知道在Flush执行的时候,camera privider正处在recovery progress 中,所以要wait recovery progress完成后再执行下一步Flush操作。wait没有返回的原因是recovery未能完成。继续在ANR 堆栈中搜索“recovery”,看看“recovery”过程中是否有被挂住的地方。 非常幸运,一下就搜到了。

        从上图中的堆栈可以看到recovery progress 最终挂在了DeferredRequestQueue::Pause函数中。

        从DeferredRequestQueue::Pause函数的源码,我们可以看出,m_pDRQPauseLock->Lock()函数被挂住了。m_pDRQPauseLock 这个锁使用的地方不多,也全部在DeferredRequestQueue这个类中。我们继续在ANR文件中搜索“DeferredRequestQueue”,看看m_pDRQPauseLock这个锁被谁持有了。

                从上图的堆栈中,可以看出线程“2334”被挂在了DeferredRequestQueue::UpdateDependency的TimedWait函数中,再结合下图代码,可以知道UpdateDependency函数已经获得m_pDRQPauseLock 锁,但是却被m_pDRQPauseWait->TimedWait这条语句挂住了。

        这里的TimedWait是高通封装的pthread_cond_timedwait,Lock是封装的pthread_mutex_lock。这是经典的pthread_cond_wait与mutex结合的用法:

        Lock->if/while 判断条件->wait->UnLock.

         在调用pthread_cond_wait()前由本线程加锁(pthread_mutex_lock()),而在判断条件进入等待队列以前,mutex保持锁定状态,并在线程挂起进入等待后解锁。在条件满足或者超时后,离开pthread_cond_wait()/pthread_cond_timedwait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。

        TimedWait具有超时退出功能,查代码,超时时间是1秒。理论上即便没有signal发出,超时后会自动退出。现在却被长久挂住了,非常奇怪!

        突然发现,Lock使用的是m_pDRQPauseLock 锁,但是传给TimedWait的参数却使用了m_pDeferredQueueLock 锁。也就是TimedWait进入等待队列的时候释放了m_pDeferredQueueLock 锁,满足条件或者超时离开的时候是在对m_pDeferredQueueLock 加锁。所以被长久挂住的原因只能是m_pDeferredQueueLock已经被别的线程持有。继续在ANR堆栈中找线索。

        这个线程是id是“2338”,也是被挂在了DeferredRequestQueue::UpdateDependency函数中,只不过是挂住点不同,这个是挂在了Lock中。结合上面对线程“2334”的分析,"2334"线程获取了m_pDRQPauseLock 锁,在TimedWait时却释放了m_pDeferredQueueLock锁,超时后在获取m_pDeferredQueueLock锁被挂住。那么线程“2338”在线程“2334”进TimeWait释放m_pDeferredQueueLock锁后获取了m_pDeferredQueueLock锁,执行下一步获取m_pDRQPauseLock 锁被挂住。 这样死锁就形成了:

        线程“2334”持有m_pDRQPauseLock 锁,等待m_pDeferredQueueLock锁。

        线程“2338”持有m_pDeferredQueueLock锁,等待m_pDRQPauseLock 锁。

        分析完后,处理就非常简单了。这个问题应该是一个简单的typo,估计程序员在写代码的时候copy错了锁,修正就可以了。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值