Merge "Limit the UserPackage cache size." into udc-dev am: 1d837f7f6e
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21611149 Change-Id: If85875d6de40d3bc55b8069ec0ff7146f542e98f Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -18,14 +18,16 @@ package android.content.pm;
|
|||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.UserIdInt;
|
import android.annotation.UserIdInt;
|
||||||
import android.os.Process;
|
|
||||||
import android.os.UserHandle;
|
|
||||||
import android.util.SparseArrayMap;
|
import android.util.SparseArrayMap;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.util.ArrayUtils;
|
import com.android.internal.util.ArrayUtils;
|
||||||
|
|
||||||
|
import libcore.util.EmptyArray;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POJO to represent a package for a specific user ID.
|
* POJO to represent a package for a specific user ID.
|
||||||
@@ -34,6 +36,16 @@ import java.util.Objects;
|
|||||||
*/
|
*/
|
||||||
public final class UserPackage {
|
public final class UserPackage {
|
||||||
private static final boolean ENABLE_CACHING = true;
|
private static final boolean ENABLE_CACHING = true;
|
||||||
|
/**
|
||||||
|
* The maximum number of entries to keep in the cache per user ID.
|
||||||
|
* The value should ideally be high enough to cover all packages on an end-user device,
|
||||||
|
* but low enough that stale or invalid packages would eventually (probably) get removed.
|
||||||
|
* This should benefit components that loop through all packages on a device and use this class,
|
||||||
|
* since being able to cache the objects for all packages on the device
|
||||||
|
* means we don't have to keep recreating the objects.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
static final int MAX_NUM_CACHED_ENTRIES_PER_USER = 1000;
|
||||||
|
|
||||||
@UserIdInt
|
@UserIdInt
|
||||||
public final int userId;
|
public final int userId;
|
||||||
@@ -43,11 +55,13 @@ public final class UserPackage {
|
|||||||
@GuardedBy("sCacheLock")
|
@GuardedBy("sCacheLock")
|
||||||
private static final SparseArrayMap<String, UserPackage> sCache = new SparseArrayMap<>();
|
private static final SparseArrayMap<String, UserPackage> sCache = new SparseArrayMap<>();
|
||||||
|
|
||||||
private static final class NoPreloadHolder {
|
/**
|
||||||
/** Set of userIDs to cache objects for. */
|
* Set of userIDs to cache objects for. We start off with an empty set, so there's no caching
|
||||||
|
* by default. The system will override with a valid set of userIDs in its process so that
|
||||||
|
* caching becomes active in the system process.
|
||||||
|
*/
|
||||||
@GuardedBy("sCacheLock")
|
@GuardedBy("sCacheLock")
|
||||||
private static int[] sUserIds = new int[]{UserHandle.getUserId(Process.myUid())};
|
private static int[] sUserIds = EmptyArray.INT;
|
||||||
}
|
|
||||||
|
|
||||||
private UserPackage(int userId, String packageName) {
|
private UserPackage(int userId, String packageName) {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
@@ -87,13 +101,14 @@ public final class UserPackage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
synchronized (sCacheLock) {
|
synchronized (sCacheLock) {
|
||||||
if (!ArrayUtils.contains(NoPreloadHolder.sUserIds, userId)) {
|
if (!ArrayUtils.contains(sUserIds, userId)) {
|
||||||
// Don't cache objects for invalid userIds.
|
// Don't cache objects for invalid userIds.
|
||||||
return new UserPackage(userId, packageName);
|
return new UserPackage(userId, packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserPackage up = sCache.get(userId, packageName);
|
UserPackage up = sCache.get(userId, packageName);
|
||||||
if (up == null) {
|
if (up == null) {
|
||||||
|
maybePurgeRandomEntriesLocked(userId);
|
||||||
packageName = packageName.intern();
|
packageName = packageName.intern();
|
||||||
up = new UserPackage(userId, packageName);
|
up = new UserPackage(userId, packageName);
|
||||||
sCache.add(userId, packageName, up);
|
sCache.add(userId, packageName, up);
|
||||||
@@ -121,7 +136,7 @@ public final class UserPackage {
|
|||||||
|
|
||||||
userIds = userIds.clone();
|
userIds = userIds.clone();
|
||||||
synchronized (sCacheLock) {
|
synchronized (sCacheLock) {
|
||||||
NoPreloadHolder.sUserIds = userIds;
|
sUserIds = userIds;
|
||||||
|
|
||||||
for (int u = sCache.numMaps() - 1; u >= 0; --u) {
|
for (int u = sCache.numMaps() - 1; u >= 0; --u) {
|
||||||
final int userId = sCache.keyAt(u);
|
final int userId = sCache.keyAt(u);
|
||||||
@@ -131,4 +146,35 @@ public final class UserPackage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public static int numEntriesForUser(int userId) {
|
||||||
|
synchronized (sCacheLock) {
|
||||||
|
return sCache.numElementsForKey(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Purge a random set of entries if the cache size is too large. */
|
||||||
|
@GuardedBy("sCacheLock")
|
||||||
|
private static void maybePurgeRandomEntriesLocked(int userId) {
|
||||||
|
final int uIdx = sCache.indexOfKey(userId);
|
||||||
|
if (uIdx < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int numCached = sCache.numElementsForKeyAt(uIdx);
|
||||||
|
if (numCached < MAX_NUM_CACHED_ENTRIES_PER_USER) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Purge a random set of 1% of cached elements for the userId. We don't want to use a
|
||||||
|
// deterministic system of purging because that may cause us to repeatedly remove elements
|
||||||
|
// that are frequently added and queried more than others. Choosing a random set
|
||||||
|
// means we will probably eventually remove less useful elements.
|
||||||
|
// An LRU cache is too expensive for this commonly used utility class.
|
||||||
|
final Random rand = new Random();
|
||||||
|
final int numToPurge = Math.max(1, MAX_NUM_CACHED_ENTRIES_PER_USER / 100);
|
||||||
|
for (int i = 0; i < numToPurge && numCached > 0; ++i) {
|
||||||
|
final int removeIdx = rand.nextInt(numCached--);
|
||||||
|
sCache.deleteAt(uIdx, removeIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,14 @@ public class SparseArrayMap<K, V> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the data for the keyIndex and mapIndex, if there was any.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public void deleteAt(int keyIndex, int mapIndex) {
|
||||||
|
mData.valueAt(keyIndex).removeAt(mapIndex);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value associated with the int-K pair.
|
* Get the value associated with the int-K pair.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.content.pm;
|
||||||
|
|
||||||
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
@Presubmit
|
||||||
|
public class UserPackageTest extends TestCase {
|
||||||
|
public void testCacheLimit() {
|
||||||
|
UserPackage.setValidUserIds(new int[]{0});
|
||||||
|
for (int i = 0; i < UserPackage.MAX_NUM_CACHED_ENTRIES_PER_USER; ++i) {
|
||||||
|
UserPackage.of(0, "app" + i);
|
||||||
|
assertEquals(i + 1, UserPackage.numEntriesForUser(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < UserPackage.MAX_NUM_CACHED_ENTRIES_PER_USER; ++i) {
|
||||||
|
UserPackage.of(0, "appOverLimit" + i);
|
||||||
|
final int numCached = UserPackage.numEntriesForUser(0);
|
||||||
|
assertTrue(numCached >= 1);
|
||||||
|
assertTrue(numCached <= UserPackage.MAX_NUM_CACHED_ENTRIES_PER_USER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user