Do not hold PM or AM locks when calling hibernation API

App hibernation calls into activity manager and package manager API. If
package manager calls into AppHibernationService while holding either
the activity manager lock or package manager locks, it can cause a
deadlock. To fix this, we ensure that app hibernation API is never
called with either lock, either by separating out the logic or putting
the app hibernation state mutation on a background thread.

Bug: 184661338
Bug: 182811830
Test: manual, hibernation app, open, see hibernation state is left
Test: atest PackageManagerServiceHibernationTests
Change-Id: I1d7dfb2232a49a63958628dd94f6b6445ece4d55
This commit is contained in:
Kevin Han
2021-04-06 13:48:06 -07:00
parent c9f96b6ea7
commit 5b3dd0e09b
2 changed files with 23 additions and 13 deletions

View File

@@ -12272,19 +12272,18 @@ public class PackageManagerService extends IPackageManager.Stub
public ArraySet<String> getOptimizablePackages() {
ArraySet<String> pkgs = new ArraySet<>();
final boolean hibernationEnabled = AppHibernationService.isAppHibernationEnabled();
AppHibernationManagerInternal appHibernationManager =
mInjector.getLocalService(AppHibernationManagerInternal.class);
synchronized (mLock) {
for (AndroidPackage p : mPackages.values()) {
// Checking hibernation state is an inexpensive call.
boolean isHibernating = hibernationEnabled
&& appHibernationManager.isHibernatingGlobally(p.getPackageName());
if (PackageDexOptimizer.canOptimizePackage(p) && !isHibernating) {
if (PackageDexOptimizer.canOptimizePackage(p)) {
pkgs.add(p.getPackageName());
}
}
}
if (AppHibernationService.isAppHibernationEnabled()) {
AppHibernationManagerInternal appHibernationManager =
mInjector.getLocalService(AppHibernationManagerInternal.class);
pkgs.removeIf(pkgName -> appHibernationManager.isHibernatingGlobally(pkgName));
}
return pkgs;
}
@@ -23465,10 +23464,12 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
if (shouldUnhibernate) {
AppHibernationManagerInternal ah =
mInjector.getLocalService(AppHibernationManagerInternal.class);
ah.setHibernatingForUser(packageName, userId, false);
ah.setHibernatingGlobally(packageName, false);
mHandler.post(() -> {
AppHibernationManagerInternal ah =
mInjector.getLocalService(AppHibernationManagerInternal.class);
ah.setHibernatingForUser(packageName, userId, false);
ah.setHibernatingGlobally(packageName, false);
});
}
}

View File

@@ -17,8 +17,12 @@
package com.android.server.pm
import android.os.Build
import android.os.Handler
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import com.android.server.apphibernation.AppHibernationManagerInternal
import com.android.server.extendedtestutils.wheneverStatic
import com.android.server.testutils.whenever
@@ -28,12 +32,12 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class PackageManagerServiceHibernationTests {
companion object {
@@ -60,6 +64,8 @@ class PackageManagerServiceHibernationTests {
rule.system().stageNominalSystemState()
whenever(rule.mocks().injector.getLocalService(AppHibernationManagerInternal::class.java))
.thenReturn(appHibernationManager)
whenever(rule.mocks().injector.handler)
.thenReturn(Handler(TestableLooper.get(this).looper))
}
@Test
@@ -74,6 +80,9 @@ class PackageManagerServiceHibernationTests {
ps!!.setStopped(true, TEST_USER_ID)
pm.setPackageStoppedState(TEST_PACKAGE_NAME, false, TEST_USER_ID)
TestableLooper.get(this).processAllMessages()
verify(appHibernationManager).setHibernatingForUser(TEST_PACKAGE_NAME, TEST_USER_ID, false)
verify(appHibernationManager).setHibernatingGlobally(TEST_PACKAGE_NAME, false)
}