Loading core/java/android/app/PropertyInvalidatedCache.java +57 −5 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; Loading Loading @@ -259,13 +260,20 @@ public abstract class PropertyInvalidatedCache<Query, Result> { @GuardedBy("sCorkLock") private static final HashMap<String, Integer> sCorks = new HashMap<>(); /** * A map of cache keys that have been disabled in the local process. When a key is * disabled locally, existing caches are disabled and the key is saved in this map. * Future cache instances that use the same key will be disabled in their constructor. */ @GuardedBy("sCorkLock") private static final HashSet<String> sDisabledKeys = new HashSet<>(); /** * Weakly references all cache objects in the current process, allowing us to iterate over * them all for purposes like issuing debug dumps and reacting to memory pressure. */ @GuardedBy("sCorkLock") private static final WeakHashMap<PropertyInvalidatedCache, Void> sCaches = new WeakHashMap<>(); private static final WeakHashMap<PropertyInvalidatedCache, Void> sCaches = new WeakHashMap<>(); private final Object mLock = new Object(); Loading Loading @@ -348,6 +356,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> { }; synchronized (sCorkLock) { sCaches.put(this, null); if (sDisabledKeys.contains(mCacheName)) { disableInstance(); } } } Loading @@ -371,6 +382,14 @@ public abstract class PropertyInvalidatedCache<Query, Result> { */ protected abstract Result recompute(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. */ protected boolean bypass(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. Loading Loading @@ -414,13 +433,37 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Disable the use of this cache in this process. */ public final void disableLocal() { public final void disableInstance() { synchronized (mLock) { mDisabled = true; clear(); } } /** * Disable the local use of all caches with the same name. All currently registered caches * 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) { synchronized (sCorkLock) { sDisabledKeys.add(name); for (PropertyInvalidatedCache cache : sCaches.keySet()) { if (name.equals(cache.mCacheName)) { cache.disableInstance(); } } } } /** * Disable this cache in the current process, and all other caches that use the same * property. */ public final void disableLocal() { disableLocal(mCacheName); } /** * Return whether the cache is disabled in this process. */ Loading @@ -435,8 +478,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> { // Let access to mDisabled race: it's atomic anyway. long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED; for (;;) { if (currentNonce == NONCE_DISABLED || currentNonce == NONCE_UNSET || currentNonce == NONCE_CORKED) { if (currentNonce == NONCE_DISABLED || currentNonce == NONCE_UNSET || currentNonce == NONCE_CORKED || bypass(query)) { if (!mDisabled) { // Do not bother collecting statistics if the cache is // locally disabled. Loading Loading @@ -874,6 +917,15 @@ public abstract class PropertyInvalidatedCache<Query, Result> { sEnabled = false; } /** * Report the disabled status of this cache instance. The return value does not * reflect status of the property key. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public boolean getDisabledState() { return isDisabledLocal(); } /** * Returns a list of caches alive at the current time. */ Loading core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java 0 → 100644 +117 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.app; import static org.junit.Assert.assertEquals; import androidx.test.filters.SmallTest; 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. * <p> * Build/Install/Run: * atest FrameworksCoreTests:PropertyInvalidatedCacheTests */ @SmallTest public class PropertyInvalidatedCacheTests { static final String CACHE_PROPERTY = "cache_key.cache_test_a"; // This class is a proxy for binder calls. It contains a counter that increments // every time the class is queried. private static class ServerProxy { // The number of times this class was queried. private int mCount = 0; // A single query. The key behavior is that the query count is incremented. boolean query(int x) { mCount++; return value(x); } // Return the expected value of an input, without incrementing the query count. boolean value(int x) { return x % 3 == 0; } // Verify the count. void verify(int x) { assertEquals(x, mCount); } } @Test public void testDisableCache1() { // A stand-in for the binder. The test verifies that calls are passed through to // this class properly. ServerProxy tester = new ServerProxy(); // Three caches, all using the same system property but one uses a different name. PropertyInvalidatedCache<Integer, Boolean> cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { @Override protected Boolean recompute(Integer x) { return tester.query(x); } }; PropertyInvalidatedCache<Integer, Boolean> cache2 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { @Override protected Boolean recompute(Integer x) { return tester.query(x); } }; PropertyInvalidatedCache<Integer, Boolean> cache3 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY, "cache3") { @Override protected Boolean recompute(Integer x) { return tester.query(x); } }; // Caches are enabled upon creation. assertEquals(false, cache1.getDisabledState()); assertEquals(false, cache2.getDisabledState()); assertEquals(false, cache3.getDisabledState()); // Disable the cache1 instance. Only cache1 is disabled cache1.disableInstance(); assertEquals(true, cache1.getDisabledState()); assertEquals(false, cache2.getDisabledState()); assertEquals(false, cache3.getDisabledState()); // Disable cache1. This will disable cache1 and cache2 because they share the // same name. cache3 has a different name and will not be disabled. cache1.disableLocal(); assertEquals(true, cache1.getDisabledState()); assertEquals(true, cache2.getDisabledState()); assertEquals(false, cache3.getDisabledState()); // Create a new cache1. Verify that the new instance is disabled. cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { @Override protected Boolean recompute(Integer x) { return tester.query(x); } }; assertEquals(true, cache1.getDisabledState()); } } Loading
core/java/android/app/PropertyInvalidatedCache.java +57 −5 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; Loading Loading @@ -259,13 +260,20 @@ public abstract class PropertyInvalidatedCache<Query, Result> { @GuardedBy("sCorkLock") private static final HashMap<String, Integer> sCorks = new HashMap<>(); /** * A map of cache keys that have been disabled in the local process. When a key is * disabled locally, existing caches are disabled and the key is saved in this map. * Future cache instances that use the same key will be disabled in their constructor. */ @GuardedBy("sCorkLock") private static final HashSet<String> sDisabledKeys = new HashSet<>(); /** * Weakly references all cache objects in the current process, allowing us to iterate over * them all for purposes like issuing debug dumps and reacting to memory pressure. */ @GuardedBy("sCorkLock") private static final WeakHashMap<PropertyInvalidatedCache, Void> sCaches = new WeakHashMap<>(); private static final WeakHashMap<PropertyInvalidatedCache, Void> sCaches = new WeakHashMap<>(); private final Object mLock = new Object(); Loading Loading @@ -348,6 +356,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> { }; synchronized (sCorkLock) { sCaches.put(this, null); if (sDisabledKeys.contains(mCacheName)) { disableInstance(); } } } Loading @@ -371,6 +382,14 @@ public abstract class PropertyInvalidatedCache<Query, Result> { */ protected abstract Result recompute(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. */ protected boolean bypass(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. Loading Loading @@ -414,13 +433,37 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Disable the use of this cache in this process. */ public final void disableLocal() { public final void disableInstance() { synchronized (mLock) { mDisabled = true; clear(); } } /** * Disable the local use of all caches with the same name. All currently registered caches * 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) { synchronized (sCorkLock) { sDisabledKeys.add(name); for (PropertyInvalidatedCache cache : sCaches.keySet()) { if (name.equals(cache.mCacheName)) { cache.disableInstance(); } } } } /** * Disable this cache in the current process, and all other caches that use the same * property. */ public final void disableLocal() { disableLocal(mCacheName); } /** * Return whether the cache is disabled in this process. */ Loading @@ -435,8 +478,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> { // Let access to mDisabled race: it's atomic anyway. long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED; for (;;) { if (currentNonce == NONCE_DISABLED || currentNonce == NONCE_UNSET || currentNonce == NONCE_CORKED) { if (currentNonce == NONCE_DISABLED || currentNonce == NONCE_UNSET || currentNonce == NONCE_CORKED || bypass(query)) { if (!mDisabled) { // Do not bother collecting statistics if the cache is // locally disabled. Loading Loading @@ -874,6 +917,15 @@ public abstract class PropertyInvalidatedCache<Query, Result> { sEnabled = false; } /** * Report the disabled status of this cache instance. The return value does not * reflect status of the property key. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public boolean getDisabledState() { return isDisabledLocal(); } /** * Returns a list of caches alive at the current time. */ Loading
core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java 0 → 100644 +117 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.app; import static org.junit.Assert.assertEquals; import androidx.test.filters.SmallTest; 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. * <p> * Build/Install/Run: * atest FrameworksCoreTests:PropertyInvalidatedCacheTests */ @SmallTest public class PropertyInvalidatedCacheTests { static final String CACHE_PROPERTY = "cache_key.cache_test_a"; // This class is a proxy for binder calls. It contains a counter that increments // every time the class is queried. private static class ServerProxy { // The number of times this class was queried. private int mCount = 0; // A single query. The key behavior is that the query count is incremented. boolean query(int x) { mCount++; return value(x); } // Return the expected value of an input, without incrementing the query count. boolean value(int x) { return x % 3 == 0; } // Verify the count. void verify(int x) { assertEquals(x, mCount); } } @Test public void testDisableCache1() { // A stand-in for the binder. The test verifies that calls are passed through to // this class properly. ServerProxy tester = new ServerProxy(); // Three caches, all using the same system property but one uses a different name. PropertyInvalidatedCache<Integer, Boolean> cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { @Override protected Boolean recompute(Integer x) { return tester.query(x); } }; PropertyInvalidatedCache<Integer, Boolean> cache2 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { @Override protected Boolean recompute(Integer x) { return tester.query(x); } }; PropertyInvalidatedCache<Integer, Boolean> cache3 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY, "cache3") { @Override protected Boolean recompute(Integer x) { return tester.query(x); } }; // Caches are enabled upon creation. assertEquals(false, cache1.getDisabledState()); assertEquals(false, cache2.getDisabledState()); assertEquals(false, cache3.getDisabledState()); // Disable the cache1 instance. Only cache1 is disabled cache1.disableInstance(); assertEquals(true, cache1.getDisabledState()); assertEquals(false, cache2.getDisabledState()); assertEquals(false, cache3.getDisabledState()); // Disable cache1. This will disable cache1 and cache2 because they share the // same name. cache3 has a different name and will not be disabled. cache1.disableLocal(); assertEquals(true, cache1.getDisabledState()); assertEquals(true, cache2.getDisabledState()); assertEquals(false, cache3.getDisabledState()); // Create a new cache1. Verify that the new instance is disabled. cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { @Override protected Boolean recompute(Integer x) { return tester.query(x); } }; assertEquals(true, cache1.getDisabledState()); } }