CTS/GTS问题分析2 | weiinter105

问题初探

测试命令: run gts-suite -s ID -o -m GtsGmscoreHostTestCases -t com.google.android.gts.devicepolicy.managedprovisioning.DeviceOwnerProvisioningHostsideTest#testRequiredAppsInManagedProfileForManagedDevice

报错堆栈:
07-27 06:59:30.611 32075 32125 I SilentProvisioningTest: managedProfileProvisionedReceiver.awaitForBroadcast(): failed
07-27 06:59:30.612 2091 2129 D ContactsDatabaseHelper: insertMimeType: vnd.android.cursor.item/website

07-27 06:59:30.613 1535 1587 W zygote64: kill(-1628, 9) failed: No such process
07-27 06:59:30.614 1535 2857 D CompatibilityInfo: mCompatibilityFlags - 10
07-27 06:59:30.614 1535 2857 D CompatibilityInfo: applicationDensity - 480
07-27 06:59:30.614 1535 2857 D CompatibilityInfo: applicationScale - 1.0
07-27 06:59:30.617 32075 32122 I TestRunner: failed: testProvision(com.google.android.gts.managedprovisioning.ManagedProfileProvisioningTest)
07-27 06:59:30.617 32075 32122 I TestRunner: ----- begin exception -----
07-27 06:59:30.618 32075 32122 I TestRunner: java.lang.AssertionError
07-27 06:59:30.618 32075 32122 I TestRunner: at org.junit.Assert.fail(Assert.java:86)
07-27 06:59:30.618 32075 32122 I TestRunner: at org.junit.Assert.assertTrue(Assert.java:41)
07-27 06:59:30.618 32075 32122 I TestRunner: at org.junit.Assert.assertTrue(Assert.java:52)
07-27 06:59:30.618 32075 32122 I TestRunner: at com.google.android.gts.managedprovisioning.ManagedProfileProvisioningTest.testProvision(ManagedProfileProvisioningTest.java:54)

从log看是case中在规定时间内没有接受到想要的广播导致的超时,那么凭借以往的经验,觉得这个case应该是偶现的

经过复测发现果然有三种情况

  1. case pass
  2. 延长广播receiver的等待时间可以pass
  3. 延长广播的等待时间也不能pass
    主要需要分析的是第三种情况,因为这才最可能是测试偶现fail的root cause

问题分析

当延长广播的等待时间也不能pass,那么说明有很大可能广播根本就没有发出去或者在传播过程中出现了问题,再查看log:
发现了关键性的log:
BroadcastQueue: Failure [foreground] sending broadcast result of Intent { act=android.app.action.PROFILE_PROVISIONING_COMPLETE flg=0x10000030 (has extras) }

重新整理case
SilentProvisioningTestManager里面注册了两个广播

</p

只要有一个receiver没有在10s内接受,那么case fail

smali加了一下log,正常能pass的版本应该是</p

 

两个广播都能收到,且会有如下log:
ACTION_PROFILE_PROVISIONING_COMPLETE broadcast received by mdm

这两个广播其实都和PROFILE_PROVISIONING_COMPLETE 这个广播有关,都是接收PROFILE_PROVISIONING_COMPLETE这个广播后才会发送

对比一下fail的log:

 

可见MANAGED_PROFILE_PROVISIONED这个广播没有等到,因为其根本没有顺利发送出来;其发送的地方DpcReceivedSuccessReceiver.java中的

 

可以看到上面onReceive中的第一行就有"ACTION_PROFILE_PROVISIONING_COMPLETE broadcast received by mdm,而现在没有这行log,说明没有接收到ACTION_PROFILE_PROVISIONING_COMPLETE这个广播,所以case等待的广播就不能发出去了,所以延长多少等待时间都会fail

再回到上面的报错,广播ACTION_PROFILE_PROVISIONING_COMPLETE发送后最后一个处理结果的receiver没有顺利接收

 

没有被接收的原因是

 

app.thread must not be null 这个是接收进程的processRecord中的thread为null时会打出的log,此时肯定不能接收到广播,而这里为null的原因是因为receiver所在进程处于被杀的过程中导致的

 

在进程被杀的过程中可能会将相应的ProcessRecord中的thread置为null

再回到log中,果然如此:

 

对比能通过的Android one版本

 

通过对比能发现果然是因为com.android.managedprovisioning在receicever接收到之前被杀导致的问题,原因单纯就是因为优先级太低了

在运行过程中不停的运行:
adb shell dumpsys activity o | grep -n2 com.android.managedprovisioning(Android p换成p,打印系统中进程信息)

 

从上面的过程可以看出,只要com.android.managedprovisioning到了后台,基本立即就变成了空进程;而通过观察进入后台的时机就是因为case会起一个Activity进行了遮挡,那么com.android.managedprovisioning就到了后台;那么首先想到的是将起这个Activity的时间延长一段时间,看看能不能通过

 

首先试了下,将startActivity之前做了一个sleep操作就可以必过了,那么从侧面说明了我们前面分析的没错;就是因为com.android.managedprovisioning变成了空进程导致的,系统杀空进程的原则是达到了空进程上限,然后开始统一杀空进程。对比了Android one的机器,空进程上限都是17,但是因为我们的系统中有miui的很多进程,导致被杀的快。理论上如果将Android one的机器缩小空进程上限,或者安装上很多app也是可能复现的,这里我们将Android one设置为不允许后台启动,相当于缩小了上限,果然在Android one上也复现了该问题

那么能想到的问题处理方案:
1.增大允许的空进程上限,但是这会造成的很大的影响,如内存什么的,为了一条case肯定是不能允许的
2.测试时将com.android.managedprovisioning保活在前台,可以利用我们MIUI的卡片机制,在测试过程中为相应进程加锁,加锁后也能通过,但是这相当于人为干预测试,其实是google不允许的

因此,最终决定和google反馈

问题总结

这个问题虽然google的case也有不合理的地方,但我觉得原生关于Broadcast的resultTo也有一点问题。发送有序广播的进程,可以设置个resultTo当作发送完的"回调",但是这个进程在发送期间是没有优先级额外的提升的,所以容易在这期间被杀;而我们看到,正常接收广播时,它们在接收期间进程ADJ会提升到FOREGROUND,所以不太容易被杀,如下:

 

 

所以,com.android.managedprovisioning 这个进程在发送广播后,在接收resultTo回调之前,因为进程的ADJ变为900+、procState变为PROCESS_STATE_CACHED_EMPTY,并且系统中所有空进程个数超过了上限(17),导致了进程被杀死,com.android.managedprovisioning的resultTo.performReceive没有被调用,最终导致了测试失败。理论上这个问题在原生的系统上,如果安装、运行的APP越多,case fail的概率就越大;

因此这个case是因为google case设计+我们的机器app安装太多+广播处理resultTo的原生逻辑共同的导致的问题

google做了修复,优化了managedprofile的流程,保证provision完全结束后再stop ProvisioningService,防止进程提前被杀,保证流程的完整性

https://android-review.googlesource.com/c/platform/packages/apps/ManagedProvisioning/+/737556

可惜patch 1的修复是有问题的,反而会导致问题必现,见后续分析

后续分析

加上google上面的patch之后问题反而变成必现的了,关键log如下:

09-11 17:32:05.666 6875 6894 I TestRunner: started: testProvision(com.google.android.gts.managedprovisioning.ManagedProfileProvisioningTest)
09-11 17:32:06.531 22290 22290 I ManagedProvisioning: Processing mininalist extras intent.
09-11 17:32:06.535 22290 22290 D ManagedProvisioning: Device is provisioned, FRP not required.
09-11 17:32:06.538 22290 22290 D ManagedProvisioning: Cancelling provisioning reminder.
09-11 17:32:06.570 22290 22290 E ManagedProvisioning: Trying to start provisioning, but it's already running
09-11 17:32:06.579 22290 22290 I ManagedProvisioning: ProvisioningActivity pre-finalization completed
09-11 17:32:16.652 6875 6896 I SilentProvisioningTest: managedProfileProvisionedReceiver.awaitForBroadcast(): failed
09-11 17:32:16.653 6875 6894 I TestRunner: failed: testProvision(com.google.android.gts.managedprovisioning.ManagedProfileProvisioningTest)

 

这样我们在测完一条case之后,执行到这里,将其置为空进程的操作已经被移到finalizationCompleted里面了,导致进程不会被杀,那么执行第二条case时其实根本没有真正执行,就fail了,也就是说google的change可能导致该进程再正常执行完后不能被杀,原因如下:

 

google将使进程变为空进程的逻辑移到了上面的新增函数中,然而如何才能调用到这里呢?

 

也就是说DpcReceivedSuccessReceiver接受到数据时才会调用ProvisioningManager中的 finalizationCompleted,进行收尾工作;而这个广播的发送是在

 

也就是说执行ManagedProfileSettingsTask时才会真正执行到finalizationCompleted,将进程杀死;然后这条case其实会执行两次Initializing provisioning process,且第一次没有执行ManagedProfileSettingsTask,因此进程不会被杀,第二次没有执行;换句话说,这个修改只是根据原来我们上报的错误修的,没有考虑过所有的执行路径,可能存在进程执行完任务无法自动被杀的情况,需要google再次修复

我的想法是在preFinalizationCompleted中将FinalizationActivity起起来,保证进程执行完task之后能被杀掉,下面是我的change

https://android-review.googlesource.com/c/platform/packages/apps/ManagedProvisioning/+/75480

最后google提供了新patch,https://partnerissuetracker.corp.google.com/u/1/issues/112177789

总体思路是通过Service进行保活,进dev观察一段时间

作者: RESSRC

个人资源站

发表评论

邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据