Loading core/java/android/app/ActivityThread.java +2 −4 Original line number Diff line number Diff line Loading @@ -1646,7 +1646,7 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) { PropertyInvalidatedCache.dumpCacheInfo(pfd.getFileDescriptor(), args); PropertyInvalidatedCache.dumpCacheInfo(pfd, args); IoUtils.closeQuietly(pfd); } Loading Loading @@ -6391,9 +6391,7 @@ public final class ActivityThread extends ClientTransactionHandler if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level); if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { for (PropertyInvalidatedCache pic : PropertyInvalidatedCache.getActiveCaches()) { pic.clear(); } PropertyInvalidatedCache.onTrimMemory(); } final ArrayList<ComponentCallbacks2> callbacks = Loading core/java/android/app/PropertyInvalidatedCache.java +73 −35 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.os.SystemProperties; import android.text.TextUtils; Loading @@ -29,7 +30,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastPrintWriter; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; Loading Loading @@ -355,11 +355,12 @@ public abstract class PropertyInvalidatedCache<Query, Result> { private final int mMaxEntries; /** * Make a new property invalidated cache. * Make a new property invalidated cache. This constructor names the cache after the * property name. New clients should prefer the constructor that takes an explicit * cache name. * * @param maxEntries Maximum number of entries to cache; LRU discard * @param propertyName Name of the system property holding the cache invalidation nonce * Defaults the cache name to the property name. * @param propertyName Name of the system property holding the cache invalidation nonce. */ public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) { this(maxEntries, propertyName, propertyName); Loading Loading @@ -418,7 +419,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * Enable or disable testing. The testing property map is cleared every time this * method is called. */ @VisibleForTesting @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static void setTestMode(boolean mode) { sTesting = mode; synchronized (sTestingPropertyMap) { Loading @@ -430,8 +431,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * Enable testing the specific cache key. Only keys in the map are subject to testing. * There is no method to stop testing a property name. Just disable the test mode. */ @VisibleForTesting public static void testPropertyName(String name) { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static void testPropertyName(@NonNull String name) { synchronized (sTestingPropertyMap) { sTestingPropertyMap.put(name, (long) NONCE_UNSET); } Loading Loading @@ -505,21 +506,23 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * block. If this function returns null, the result of the cache query is null. There is no * "negative cache" in the query: we don't cache null results at all. */ public abstract Result recompute(Query query); public abstract @NonNull Result recompute(@NonNull Query query); /** * Return true if the query should bypass the cache. The default behavior is to * always use the cache but the method can be overridden for a specific class. */ public boolean bypass(Query query) { public boolean bypass(@NonNull Query query) { return false; } /** * Determines if a pair of responses are considered equal. Used to determine whether * a cache is inadvertently returning stale results when VERIFY is set to true. * Determines if a pair of responses are considered equal. Used to determine whether a * cache is inadvertently returning stale results when VERIFY is set to true. Some * existing clients override this method, but it is now deprecated in favor of a valid * equals() method on the Result class. */ protected boolean resultEquals(Result cachedResult, Result fetchedResult) { public boolean resultEquals(Result cachedResult, Result fetchedResult) { // If a service crashes and returns a null result, the cached value remains valid. if (fetchedResult != null) { return Objects.equals(cachedResult, fetchedResult); Loading @@ -544,8 +547,11 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** * Disable the use of this cache in this process. * Disable the use of this cache in this process. This method is using during * testing. To disable a cache in normal code, use disableLocal(). * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final void disableInstance() { synchronized (mLock) { mDisabled = true; Loading @@ -558,7 +564,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * using the key will be disabled now, and all future cache instances that use the key will be * disabled in their constructor. */ public static final void disableLocal(@NonNull String name) { private static final void disableLocal(@NonNull String name) { synchronized (sCorkLock) { sDisabledKeys.add(name); for (PropertyInvalidatedCache cache : sCaches.keySet()) { Loading @@ -569,8 +575,22 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } } /** * Stop disabling local caches with a particular name. Any caches that are currently * disabled remain disabled (the "disabled" setting is sticky). However, new caches * with this name will not be disabled. It is not an error if the cache name is not * found in the list of disabled caches. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final void clearDisableLocal() { synchronized (sCorkLock) { sDisabledKeys.remove(mCacheName); } } /** * Disable this cache in the current process, and all other caches that use the same * name. This does not affect caches that have a different name but use the same * property. */ public final void disableLocal() { Loading @@ -580,6 +600,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Return whether the cache is disabled in this process. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final boolean isDisabledLocal() { return mDisabled || !sEnabled; } Loading @@ -587,7 +608,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Get a value from the cache or recompute it. */ public Result query(Query query) { public @NonNull Result query(@NonNull Query query) { // Let access to mDisabled race: it's atomic anyway. long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED; if (bypass(query)) { Loading Loading @@ -704,6 +725,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * the cache objects to invalidate all of the cache objects becomes confusing and you should * just use the static version of this function. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final void disableSystemWide() { disableSystemWide(mPropertyName); } Loading @@ -714,7 +736,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * * @param name Name of the cache-key property to invalidate */ public static void disableSystemWide(@NonNull String name) { private static void disableSystemWide(@NonNull String name) { if (!sEnabled) { return; } Loading Loading @@ -784,8 +806,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> { "invalidating cache [%s]: [%s] -> [%s]", name, nonce, Long.toString(newValue))); } // TODO(dancol): add an atomic compare and exchange property set operation to avoid a // small race with concurrent disable here. // There is a small race with concurrent disables here. A compare-and-exchange // property operation would be required to eliminate the race condition. setNonce(name, newValue); long invalidateCount = sInvalidates.getOrDefault(name, (long) 0); sInvalidates.put(name, ++invalidateCount); Loading Loading @@ -990,6 +1012,10 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } } /** * Return the result generated by a given query to the cache, performing debugging checks when * enabled. */ private Result maybeCheckConsistency(Query query, Result proposedResult) { if (VERIFY) { Result resultToCompare = recompute(query); Loading @@ -1007,24 +1033,27 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** * Return the name of the cache, to be used in debug messages. The * method is public so clients can use it. * Return the name of the cache, to be used in debug messages. */ public String cacheName() { private final @NonNull String cacheName() { return mCacheName; } /** * Return the query as a string, to be used in debug messages. The * method is public so clients can use it in external debug messages. * Return the query as a string, to be used in debug messages. New clients should not * override this, but should instead add the necessary toString() method to the Query * class. */ public String queryToString(Query query) { protected @NonNull String queryToString(@NonNull Query query) { return Objects.toString(query); } /** * Disable all caches in the local process. Once disabled it is not * possible to re-enable caching in the current process. * Disable all caches in the local process. This is primarily useful for testing when * the test needs to bypass the cache or when the test is for a server, and the test * process does not have privileges to write SystemProperties. Once disabled it is not * possible to re-enable caching in the current process. If a client wants to * temporarily disable caching, use the corking mechanism. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static void disableForTestMode() { Loading @@ -1044,7 +1073,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Returns a list of caches alive at the current time. */ public static ArrayList<PropertyInvalidatedCache> getActiveCaches() { private static @NonNull ArrayList<PropertyInvalidatedCache> getActiveCaches() { synchronized (sCorkLock) { return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet()); } Loading @@ -1053,7 +1082,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Returns a list of the active corks in a process. */ public static ArrayList<Map.Entry<String, Integer>> getActiveCorks() { private static @NonNull ArrayList<Map.Entry<String, Integer>> getActiveCorks() { synchronized (sCorkLock) { return new ArrayList<Map.Entry<String, Integer>>(sCorks.entrySet()); } Loading Loading @@ -1104,14 +1133,14 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** * Dumps contents of every cache in the process to the provided FileDescriptor. * Dumps the contents of every cache in the process to the provided ParcelFileDescriptor. */ public static void dumpCacheInfo(FileDescriptor fd, String[] args) { public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) { ArrayList<PropertyInvalidatedCache> activeCaches; ArrayList<Map.Entry<String, Integer>> activeCorks; try ( FileOutputStream fout = new FileOutputStream(fd); FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor()); PrintWriter pw = new FastPrintWriter(fout); ) { if (!sEnabled) { Loading Loading @@ -1142,4 +1171,13 @@ public abstract class PropertyInvalidatedCache<Query, Result> { Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances"); } } /** * Trim memory by clearing all the caches. */ public static void onTrimMemory() { for (PropertyInvalidatedCache pic : getActiveCaches()) { pic.clear(); } } } core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +165 −2 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package android.app; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import androidx.test.filters.SmallTest; Loading @@ -25,7 +27,9 @@ import org.junit.Test; /** * Test for verifying the behavior of {@link PropertyInvalidatedCache}. This test does * not use any actual binder calls - it is entirely self-contained. * not use any actual binder calls - it is entirely self-contained. This test also relies * on the test mode of {@link PropertyInvalidatedCache} because Android SELinux rules do * not grant test processes the permission to set system properties. * <p> * Build/Install/Run: * atest FrameworksCoreTests:PropertyInvalidatedCacheTests Loading @@ -33,6 +37,8 @@ import org.junit.Test; @SmallTest public class PropertyInvalidatedCacheTests { // This property is never set. The test process does not have permission to set any // properties. static final String CACHE_PROPERTY = "cache_key.cache_test_a"; // This class is a proxy for binder calls. It contains a counter that increments Loading @@ -58,7 +64,8 @@ public class PropertyInvalidatedCacheTests { } } // Clear the test mode after every test, in case this process is used for other tests. // Clear the test mode after every test, in case this process is used for other // tests. This also resets the test property map. @After public void tearDown() throws Exception { PropertyInvalidatedCache.setTestMode(false); Loading Loading @@ -176,5 +183,161 @@ public class PropertyInvalidatedCacheTests { } }; assertEquals(true, cache1.getDisabledState()); // Remove the record of caches being locally disabled. This is a clean-up step. cache1.clearDisableLocal(); assertEquals(true, cache1.getDisabledState()); assertEquals(true, cache2.getDisabledState()); assertEquals(false, cache3.getDisabledState()); // Create a new cache1. Verify that the new instance is not disabled. cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { @Override public Boolean recompute(Integer x) { return tester.query(x); } }; assertEquals(false, cache1.getDisabledState()); } private static final String UNSET_KEY = "Aiw7woh6ie4toh7W"; private static class TestCache extends PropertyInvalidatedCache<Integer, String> { TestCache() { this(CACHE_PROPERTY); } TestCache(String key) { super(4, key); setTestMode(true); testPropertyName(key); } @Override public String recompute(Integer qv) { mRecomputeCount += 1; return "foo" + qv.toString(); } int getRecomputeCount() { return mRecomputeCount; } private int mRecomputeCount = 0; } @Test public void testCacheRecompute() { TestCache cache = new TestCache(); cache.invalidateCache(); assertEquals(cache.isDisabledLocal(), false); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo6", cache.query(6)); assertEquals(2, cache.getRecomputeCount()); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } @Test public void testCacheInitialState() { TestCache cache = new TestCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } @Test public void testCachePropertyUnset() { TestCache cache = new TestCache(UNSET_KEY); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); } @Test public void testCacheDisableState() { TestCache cache = new TestCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); cache.disableSystemWide(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(5, cache.getRecomputeCount()); cache.invalidateCache(); // Should not reenable assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(7, cache.getRecomputeCount()); } @Test public void testRefreshSameObject() { int[] refreshCount = new int[1]; TestCache cache = new TestCache() { @Override public String refresh(String oldResult, Integer query) { refreshCount[0] += 1; return oldResult; } }; cache.invalidateCache(); String result1 = cache.query(5); assertEquals("foo5", result1); String result2 = cache.query(5); assertSame(result1, result2); assertEquals(1, cache.getRecomputeCount()); assertEquals(1, refreshCount[0]); assertEquals("foo5", cache.query(5)); assertEquals(2, refreshCount[0]); } @Test public void testRefreshInvalidateRace() { int[] refreshCount = new int[1]; TestCache cache = new TestCache() { @Override public String refresh(String oldResult, Integer query) { refreshCount[0] += 1; invalidateCache(); return new String(oldResult); } }; cache.invalidateCache(); String result1 = cache.query(5); assertEquals("foo5", result1); String result2 = cache.query(5); assertEquals(result1, result2); assertNotSame(result1, result2); assertEquals(2, cache.getRecomputeCount()); } @Test public void testLocalProcessDisable() { TestCache cache = new TestCache(); assertEquals(cache.isDisabledLocal(), false); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals(cache.isDisabledLocal(), false); cache.disableLocal(); assertEquals(cache.isDisabledLocal(), true); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } } core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.javadeleted 100644 → 0 +0 −168 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.os; import android.app.PropertyInvalidatedCache; import android.test.suitebuilder.annotation.SmallTest; import junit.framework.TestCase; public class PropertyInvalidatedCacheTest extends TestCase { private static final String KEY = "sys.testkey"; private static final String UNSET_KEY = "Aiw7woh6ie4toh7W"; private static class TestCache extends PropertyInvalidatedCache<Integer, String> { TestCache() { this(KEY); } TestCache(String key) { super(4, key); } @Override public String recompute(Integer qv) { mRecomputeCount += 1; return "foo" + qv.toString(); } int getRecomputeCount() { return mRecomputeCount; } private int mRecomputeCount = 0; } @Override protected void setUp() { SystemProperties.set(KEY, ""); } @SmallTest public void testCacheRecompute() throws Exception { TestCache cache = new TestCache(); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo6", cache.query(6)); assertEquals(2, cache.getRecomputeCount()); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } @SmallTest public void testCacheInitialState() throws Exception { TestCache cache = new TestCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } @SmallTest public void testCachePropertyUnset() throws Exception { TestCache cache = new TestCache(UNSET_KEY); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); } @SmallTest public void testCacheDisableState() throws Exception { TestCache cache = new TestCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); cache.disableSystemWide(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(5, cache.getRecomputeCount()); cache.invalidateCache(); // Should not reenable assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(7, cache.getRecomputeCount()); } @SmallTest public void testRefreshSameObject() throws Exception { int[] refreshCount = new int[1]; TestCache cache = new TestCache() { @Override protected String refresh(String oldResult, Integer query) { refreshCount[0] += 1; return oldResult; } }; cache.invalidateCache(); String result1 = cache.query(5); assertEquals("foo5", result1); String result2 = cache.query(5); assertSame(result1, result2); assertEquals(1, cache.getRecomputeCount()); assertEquals(1, refreshCount[0]); assertEquals("foo5", cache.query(5)); assertEquals(2, refreshCount[0]); } @SmallTest public void testRefreshInvalidateRace() throws Exception { int[] refreshCount = new int[1]; TestCache cache = new TestCache() { @Override protected String refresh(String oldResult, Integer query) { refreshCount[0] += 1; invalidateCache(); return new String(oldResult); } }; cache.invalidateCache(); String result1 = cache.query(5); assertEquals("foo5", result1); String result2 = cache.query(5); assertEquals(result1, result2); assertNotSame(result1, result2); assertEquals(2, cache.getRecomputeCount()); } @SmallTest public void testLocalProcessDisable() throws Exception { TestCache cache = new TestCache(); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals(cache.isDisabledLocal(), false); cache.disableLocal(); assertEquals(cache.isDisabledLocal(), true); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } } services/core/java/com/android/server/am/ActivityManagerService.java +0 −4 Original line number Diff line number Diff line Loading @@ -10360,10 +10360,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (thread != null) { pw.println("\n\n** Cache info for pid " + pid + " [" + r.processName + "] **"); pw.flush(); if (pid == MY_PID) { PropertyInvalidatedCache.dumpCacheInfo(fd, args); continue; } try { TransferPipe tp = new TransferPipe(); try { Loading Loading
core/java/android/app/ActivityThread.java +2 −4 Original line number Diff line number Diff line Loading @@ -1646,7 +1646,7 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) { PropertyInvalidatedCache.dumpCacheInfo(pfd.getFileDescriptor(), args); PropertyInvalidatedCache.dumpCacheInfo(pfd, args); IoUtils.closeQuietly(pfd); } Loading Loading @@ -6391,9 +6391,7 @@ public final class ActivityThread extends ClientTransactionHandler if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level); if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { for (PropertyInvalidatedCache pic : PropertyInvalidatedCache.getActiveCaches()) { pic.clear(); } PropertyInvalidatedCache.onTrimMemory(); } final ArrayList<ComponentCallbacks2> callbacks = Loading
core/java/android/app/PropertyInvalidatedCache.java +73 −35 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.os.SystemProperties; import android.text.TextUtils; Loading @@ -29,7 +30,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastPrintWriter; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; Loading Loading @@ -355,11 +355,12 @@ public abstract class PropertyInvalidatedCache<Query, Result> { private final int mMaxEntries; /** * Make a new property invalidated cache. * Make a new property invalidated cache. This constructor names the cache after the * property name. New clients should prefer the constructor that takes an explicit * cache name. * * @param maxEntries Maximum number of entries to cache; LRU discard * @param propertyName Name of the system property holding the cache invalidation nonce * Defaults the cache name to the property name. * @param propertyName Name of the system property holding the cache invalidation nonce. */ public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) { this(maxEntries, propertyName, propertyName); Loading Loading @@ -418,7 +419,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * Enable or disable testing. The testing property map is cleared every time this * method is called. */ @VisibleForTesting @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static void setTestMode(boolean mode) { sTesting = mode; synchronized (sTestingPropertyMap) { Loading @@ -430,8 +431,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * Enable testing the specific cache key. Only keys in the map are subject to testing. * There is no method to stop testing a property name. Just disable the test mode. */ @VisibleForTesting public static void testPropertyName(String name) { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static void testPropertyName(@NonNull String name) { synchronized (sTestingPropertyMap) { sTestingPropertyMap.put(name, (long) NONCE_UNSET); } Loading Loading @@ -505,21 +506,23 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * block. If this function returns null, the result of the cache query is null. There is no * "negative cache" in the query: we don't cache null results at all. */ public abstract Result recompute(Query query); public abstract @NonNull Result recompute(@NonNull Query query); /** * Return true if the query should bypass the cache. The default behavior is to * always use the cache but the method can be overridden for a specific class. */ public boolean bypass(Query query) { public boolean bypass(@NonNull Query query) { return false; } /** * Determines if a pair of responses are considered equal. Used to determine whether * a cache is inadvertently returning stale results when VERIFY is set to true. * Determines if a pair of responses are considered equal. Used to determine whether a * cache is inadvertently returning stale results when VERIFY is set to true. Some * existing clients override this method, but it is now deprecated in favor of a valid * equals() method on the Result class. */ protected boolean resultEquals(Result cachedResult, Result fetchedResult) { public boolean resultEquals(Result cachedResult, Result fetchedResult) { // If a service crashes and returns a null result, the cached value remains valid. if (fetchedResult != null) { return Objects.equals(cachedResult, fetchedResult); Loading @@ -544,8 +547,11 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** * Disable the use of this cache in this process. * Disable the use of this cache in this process. This method is using during * testing. To disable a cache in normal code, use disableLocal(). * @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final void disableInstance() { synchronized (mLock) { mDisabled = true; Loading @@ -558,7 +564,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * using the key will be disabled now, and all future cache instances that use the key will be * disabled in their constructor. */ public static final void disableLocal(@NonNull String name) { private static final void disableLocal(@NonNull String name) { synchronized (sCorkLock) { sDisabledKeys.add(name); for (PropertyInvalidatedCache cache : sCaches.keySet()) { Loading @@ -569,8 +575,22 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } } /** * Stop disabling local caches with a particular name. Any caches that are currently * disabled remain disabled (the "disabled" setting is sticky). However, new caches * with this name will not be disabled. It is not an error if the cache name is not * found in the list of disabled caches. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final void clearDisableLocal() { synchronized (sCorkLock) { sDisabledKeys.remove(mCacheName); } } /** * Disable this cache in the current process, and all other caches that use the same * name. This does not affect caches that have a different name but use the same * property. */ public final void disableLocal() { Loading @@ -580,6 +600,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Return whether the cache is disabled in this process. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final boolean isDisabledLocal() { return mDisabled || !sEnabled; } Loading @@ -587,7 +608,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Get a value from the cache or recompute it. */ public Result query(Query query) { public @NonNull Result query(@NonNull Query query) { // Let access to mDisabled race: it's atomic anyway. long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED; if (bypass(query)) { Loading Loading @@ -704,6 +725,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * the cache objects to invalidate all of the cache objects becomes confusing and you should * just use the static version of this function. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final void disableSystemWide() { disableSystemWide(mPropertyName); } Loading @@ -714,7 +736,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * * @param name Name of the cache-key property to invalidate */ public static void disableSystemWide(@NonNull String name) { private static void disableSystemWide(@NonNull String name) { if (!sEnabled) { return; } Loading Loading @@ -784,8 +806,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> { "invalidating cache [%s]: [%s] -> [%s]", name, nonce, Long.toString(newValue))); } // TODO(dancol): add an atomic compare and exchange property set operation to avoid a // small race with concurrent disable here. // There is a small race with concurrent disables here. A compare-and-exchange // property operation would be required to eliminate the race condition. setNonce(name, newValue); long invalidateCount = sInvalidates.getOrDefault(name, (long) 0); sInvalidates.put(name, ++invalidateCount); Loading Loading @@ -990,6 +1012,10 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } } /** * Return the result generated by a given query to the cache, performing debugging checks when * enabled. */ private Result maybeCheckConsistency(Query query, Result proposedResult) { if (VERIFY) { Result resultToCompare = recompute(query); Loading @@ -1007,24 +1033,27 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** * Return the name of the cache, to be used in debug messages. The * method is public so clients can use it. * Return the name of the cache, to be used in debug messages. */ public String cacheName() { private final @NonNull String cacheName() { return mCacheName; } /** * Return the query as a string, to be used in debug messages. The * method is public so clients can use it in external debug messages. * Return the query as a string, to be used in debug messages. New clients should not * override this, but should instead add the necessary toString() method to the Query * class. */ public String queryToString(Query query) { protected @NonNull String queryToString(@NonNull Query query) { return Objects.toString(query); } /** * Disable all caches in the local process. Once disabled it is not * possible to re-enable caching in the current process. * Disable all caches in the local process. This is primarily useful for testing when * the test needs to bypass the cache or when the test is for a server, and the test * process does not have privileges to write SystemProperties. Once disabled it is not * possible to re-enable caching in the current process. If a client wants to * temporarily disable caching, use the corking mechanism. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static void disableForTestMode() { Loading @@ -1044,7 +1073,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Returns a list of caches alive at the current time. */ public static ArrayList<PropertyInvalidatedCache> getActiveCaches() { private static @NonNull ArrayList<PropertyInvalidatedCache> getActiveCaches() { synchronized (sCorkLock) { return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet()); } Loading @@ -1053,7 +1082,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Returns a list of the active corks in a process. */ public static ArrayList<Map.Entry<String, Integer>> getActiveCorks() { private static @NonNull ArrayList<Map.Entry<String, Integer>> getActiveCorks() { synchronized (sCorkLock) { return new ArrayList<Map.Entry<String, Integer>>(sCorks.entrySet()); } Loading Loading @@ -1104,14 +1133,14 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** * Dumps contents of every cache in the process to the provided FileDescriptor. * Dumps the contents of every cache in the process to the provided ParcelFileDescriptor. */ public static void dumpCacheInfo(FileDescriptor fd, String[] args) { public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) { ArrayList<PropertyInvalidatedCache> activeCaches; ArrayList<Map.Entry<String, Integer>> activeCorks; try ( FileOutputStream fout = new FileOutputStream(fd); FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor()); PrintWriter pw = new FastPrintWriter(fout); ) { if (!sEnabled) { Loading Loading @@ -1142,4 +1171,13 @@ public abstract class PropertyInvalidatedCache<Query, Result> { Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances"); } } /** * Trim memory by clearing all the caches. */ public static void onTrimMemory() { for (PropertyInvalidatedCache pic : getActiveCaches()) { pic.clear(); } } }
core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +165 −2 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package android.app; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import androidx.test.filters.SmallTest; Loading @@ -25,7 +27,9 @@ import org.junit.Test; /** * Test for verifying the behavior of {@link PropertyInvalidatedCache}. This test does * not use any actual binder calls - it is entirely self-contained. * not use any actual binder calls - it is entirely self-contained. This test also relies * on the test mode of {@link PropertyInvalidatedCache} because Android SELinux rules do * not grant test processes the permission to set system properties. * <p> * Build/Install/Run: * atest FrameworksCoreTests:PropertyInvalidatedCacheTests Loading @@ -33,6 +37,8 @@ import org.junit.Test; @SmallTest public class PropertyInvalidatedCacheTests { // This property is never set. The test process does not have permission to set any // properties. static final String CACHE_PROPERTY = "cache_key.cache_test_a"; // This class is a proxy for binder calls. It contains a counter that increments Loading @@ -58,7 +64,8 @@ public class PropertyInvalidatedCacheTests { } } // Clear the test mode after every test, in case this process is used for other tests. // Clear the test mode after every test, in case this process is used for other // tests. This also resets the test property map. @After public void tearDown() throws Exception { PropertyInvalidatedCache.setTestMode(false); Loading Loading @@ -176,5 +183,161 @@ public class PropertyInvalidatedCacheTests { } }; assertEquals(true, cache1.getDisabledState()); // Remove the record of caches being locally disabled. This is a clean-up step. cache1.clearDisableLocal(); assertEquals(true, cache1.getDisabledState()); assertEquals(true, cache2.getDisabledState()); assertEquals(false, cache3.getDisabledState()); // Create a new cache1. Verify that the new instance is not disabled. cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { @Override public Boolean recompute(Integer x) { return tester.query(x); } }; assertEquals(false, cache1.getDisabledState()); } private static final String UNSET_KEY = "Aiw7woh6ie4toh7W"; private static class TestCache extends PropertyInvalidatedCache<Integer, String> { TestCache() { this(CACHE_PROPERTY); } TestCache(String key) { super(4, key); setTestMode(true); testPropertyName(key); } @Override public String recompute(Integer qv) { mRecomputeCount += 1; return "foo" + qv.toString(); } int getRecomputeCount() { return mRecomputeCount; } private int mRecomputeCount = 0; } @Test public void testCacheRecompute() { TestCache cache = new TestCache(); cache.invalidateCache(); assertEquals(cache.isDisabledLocal(), false); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo6", cache.query(6)); assertEquals(2, cache.getRecomputeCount()); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } @Test public void testCacheInitialState() { TestCache cache = new TestCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } @Test public void testCachePropertyUnset() { TestCache cache = new TestCache(UNSET_KEY); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); } @Test public void testCacheDisableState() { TestCache cache = new TestCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); cache.disableSystemWide(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(5, cache.getRecomputeCount()); cache.invalidateCache(); // Should not reenable assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(7, cache.getRecomputeCount()); } @Test public void testRefreshSameObject() { int[] refreshCount = new int[1]; TestCache cache = new TestCache() { @Override public String refresh(String oldResult, Integer query) { refreshCount[0] += 1; return oldResult; } }; cache.invalidateCache(); String result1 = cache.query(5); assertEquals("foo5", result1); String result2 = cache.query(5); assertSame(result1, result2); assertEquals(1, cache.getRecomputeCount()); assertEquals(1, refreshCount[0]); assertEquals("foo5", cache.query(5)); assertEquals(2, refreshCount[0]); } @Test public void testRefreshInvalidateRace() { int[] refreshCount = new int[1]; TestCache cache = new TestCache() { @Override public String refresh(String oldResult, Integer query) { refreshCount[0] += 1; invalidateCache(); return new String(oldResult); } }; cache.invalidateCache(); String result1 = cache.query(5); assertEquals("foo5", result1); String result2 = cache.query(5); assertEquals(result1, result2); assertNotSame(result1, result2); assertEquals(2, cache.getRecomputeCount()); } @Test public void testLocalProcessDisable() { TestCache cache = new TestCache(); assertEquals(cache.isDisabledLocal(), false); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals(cache.isDisabledLocal(), false); cache.disableLocal(); assertEquals(cache.isDisabledLocal(), true); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } }
core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.javadeleted 100644 → 0 +0 −168 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.os; import android.app.PropertyInvalidatedCache; import android.test.suitebuilder.annotation.SmallTest; import junit.framework.TestCase; public class PropertyInvalidatedCacheTest extends TestCase { private static final String KEY = "sys.testkey"; private static final String UNSET_KEY = "Aiw7woh6ie4toh7W"; private static class TestCache extends PropertyInvalidatedCache<Integer, String> { TestCache() { this(KEY); } TestCache(String key) { super(4, key); } @Override public String recompute(Integer qv) { mRecomputeCount += 1; return "foo" + qv.toString(); } int getRecomputeCount() { return mRecomputeCount; } private int mRecomputeCount = 0; } @Override protected void setUp() { SystemProperties.set(KEY, ""); } @SmallTest public void testCacheRecompute() throws Exception { TestCache cache = new TestCache(); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo6", cache.query(6)); assertEquals(2, cache.getRecomputeCount()); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } @SmallTest public void testCacheInitialState() throws Exception { TestCache cache = new TestCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } @SmallTest public void testCachePropertyUnset() throws Exception { TestCache cache = new TestCache(UNSET_KEY); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); } @SmallTest public void testCacheDisableState() throws Exception { TestCache cache = new TestCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); cache.disableSystemWide(); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(5, cache.getRecomputeCount()); cache.invalidateCache(); // Should not reenable assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(7, cache.getRecomputeCount()); } @SmallTest public void testRefreshSameObject() throws Exception { int[] refreshCount = new int[1]; TestCache cache = new TestCache() { @Override protected String refresh(String oldResult, Integer query) { refreshCount[0] += 1; return oldResult; } }; cache.invalidateCache(); String result1 = cache.query(5); assertEquals("foo5", result1); String result2 = cache.query(5); assertSame(result1, result2); assertEquals(1, cache.getRecomputeCount()); assertEquals(1, refreshCount[0]); assertEquals("foo5", cache.query(5)); assertEquals(2, refreshCount[0]); } @SmallTest public void testRefreshInvalidateRace() throws Exception { int[] refreshCount = new int[1]; TestCache cache = new TestCache() { @Override protected String refresh(String oldResult, Integer query) { refreshCount[0] += 1; invalidateCache(); return new String(oldResult); } }; cache.invalidateCache(); String result1 = cache.query(5); assertEquals("foo5", result1); String result2 = cache.query(5); assertEquals(result1, result2); assertNotSame(result1, result2); assertEquals(2, cache.getRecomputeCount()); } @SmallTest public void testLocalProcessDisable() throws Exception { TestCache cache = new TestCache(); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals(cache.isDisabledLocal(), false); cache.disableLocal(); assertEquals(cache.isDisabledLocal(), true); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } }
services/core/java/com/android/server/am/ActivityManagerService.java +0 −4 Original line number Diff line number Diff line Loading @@ -10360,10 +10360,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (thread != null) { pw.println("\n\n** Cache info for pid " + pid + " [" + r.processName + "] **"); pw.flush(); if (pid == MY_PID) { PropertyInvalidatedCache.dumpCacheInfo(fd, args); continue; } try { TransferPipe tp = new TransferPipe(); try { Loading