diff options
| -rw-r--r-- | core/java/android/app/PropertyInvalidatedCache.java | 98 |
1 files changed, 62 insertions, 36 deletions
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 13934e592d40..a51b9d3956df 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -391,7 +391,12 @@ public class PropertyInvalidatedCache<Query, Result> { private static final boolean DEBUG = false; private static final boolean VERIFY = false; - // Per-Cache performance counters. As some cache instances are declared static, + /** + * The object-private lock. + */ + private final Object mLock = new Object(); + + // Per-Cache performance counters. @GuardedBy("mLock") private long mHits = 0; @@ -410,25 +415,19 @@ public class PropertyInvalidatedCache<Query, Result> { @GuardedBy("mLock") private long mClears = 0; - // Most invalidation is done in a static context, so the counters need to be accessible. - @GuardedBy("sCorkLock") - private static final HashMap<String, Long> sInvalidates = new HashMap<>(); - /** - * Record the number of invalidate or cork calls that were nops because - * the cache was already corked. This is static because invalidation is - * done in a static context. + * Protect objects that support corking. mLock and sGlobalLock must never be taken while this + * is held. */ - @GuardedBy("sCorkLock") - private static final HashMap<String, Long> sCorkedInvalidates = new HashMap<>(); + private static final Object sCorkLock = new Object(); /** - * If sEnabled is false then all cache operations are stubbed out. Set - * it to false inside test processes. + * Record the number of invalidate or cork calls that were nops because the cache was already + * corked. This is static because invalidation is done in a static context. Entries are + * indexed by the cache property. */ - private static boolean sEnabled = true; - - private static final Object sCorkLock = new Object(); + @GuardedBy("sCorkLock") + private static final HashMap<String, Long> sCorkedInvalidates = new HashMap<>(); /** * A map of cache keys that we've "corked". (The values are counts.) When a cache key is @@ -440,21 +439,39 @@ public class PropertyInvalidatedCache<Query, Result> { private static final HashMap<String, Integer> sCorks = new HashMap<>(); /** + * A lock for the global list of caches and cache keys. This must never be taken inside mLock + * or sCorkLock. + */ + private static final Object sGlobalLock = new Object(); + + /** * 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") + @GuardedBy("sGlobalLock") 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") + @GuardedBy("sGlobalLock") private static final WeakHashMap<PropertyInvalidatedCache, Void> sCaches = new WeakHashMap<>(); - private final Object mLock = new Object(); + /** + * Counts of the number of times a cache key was invalidated. Invalidation occurs in a static + * context with no cache object available, so this is a static map. Entries are indexed by + * the cache property. + */ + @GuardedBy("sGlobalLock") + private static final HashMap<String, Long> sInvalidates = new HashMap<>(); + + /** + * If sEnabled is false then all cache operations are stubbed out. Set + * it to false inside test processes. + */ + private static boolean sEnabled = true; /** * Name of the property that holds the unique value that we use to invalidate the cache. @@ -595,14 +612,17 @@ public class PropertyInvalidatedCache<Query, Result> { }; } - // Register the map in the global list. If the cache is disabled globally, disable it - // now. + /** + * Register the map in the global list. If the cache is disabled globally, disable it + * now. This method is only ever called from the constructor, which means no other thread has + * access to the object yet. It can safely be modified outside any lock. + */ private void registerCache() { - synchronized (sCorkLock) { - sCaches.put(this, null); + synchronized (sGlobalLock) { if (sDisabledKeys.contains(mCacheName)) { disableInstance(); } + sCaches.put(this, null); } } @@ -797,8 +817,9 @@ public class PropertyInvalidatedCache<Query, Result> { } /** - * Disable the use of this cache in this process. This method is using during - * testing. To disable a cache in normal code, use disableLocal(). + * Disable the use of this cache in this process. This method is using internally and during + * testing. To disable a cache in normal code, use disableLocal(). A disabled cache cannot + * be re-enabled. * @hide */ @TestApi @@ -811,17 +832,23 @@ public class PropertyInvalidatedCache<Query, Result> { /** * 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. + * with the name will be disabled now, and all future cache instances that use the name will + * be disabled in their constructor. */ private static final void disableLocal(@NonNull String name) { - synchronized (sCorkLock) { - sDisabledKeys.add(name); + synchronized (sGlobalLock) { + if (sDisabledKeys.contains(name)) { + // The key is already in recorded so there is no further work to be done. + return; + } for (PropertyInvalidatedCache cache : sCaches.keySet()) { if (name.equals(cache.mCacheName)) { cache.disableInstance(); } } + // Record the disabled key after the iteration. If an exception occurs during the + // iteration above, and the code is retried, the function should not exit early. + sDisabledKeys.add(name); } } @@ -834,7 +861,7 @@ public class PropertyInvalidatedCache<Query, Result> { */ @TestApi public final void forgetDisableLocal() { - synchronized (sCorkLock) { + synchronized (sGlobalLock) { sDisabledKeys.remove(mCacheName); } } @@ -851,9 +878,9 @@ public class PropertyInvalidatedCache<Query, Result> { } /** - * 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. + * Disable this cache in the current process, and all other present and future caches that use + * the same name. This does not affect caches that have a different name but use the same + * property. Once disabled, a cache cannot be reenabled. * @hide */ @TestApi @@ -1381,10 +1408,9 @@ public class PropertyInvalidatedCache<Query, Result> { /** * Returns a list of caches alive at the current time. */ + @GuardedBy("sGlobalLock") private static @NonNull ArrayList<PropertyInvalidatedCache> getActiveCaches() { - synchronized (sCorkLock) { - return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet()); - } + return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet()); } /** @@ -1540,7 +1566,7 @@ public class PropertyInvalidatedCache<Query, Result> { boolean detail = anyDetailed(args); ArrayList<PropertyInvalidatedCache> activeCaches; - synchronized (sCorkLock) { + synchronized (sGlobalLock) { activeCaches = getActiveCaches(); if (!detail) { dumpCorkInfo(pw); |