• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app;
18 
19 import android.annotation.NonNull;
20 import android.os.Handler;
21 import android.os.Looper;
22 import android.os.Message;
23 import android.os.SystemClock;
24 import android.os.SystemProperties;
25 import android.util.Log;
26 
27 import com.android.internal.annotations.GuardedBy;
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.internal.util.FastPrintWriter;
30 
31 import java.io.FileDescriptor;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 import java.io.PrintWriter;
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.LinkedHashMap;
39 import java.util.Map;
40 import java.util.Objects;
41 import java.util.Random;
42 import java.util.Set;
43 import java.util.WeakHashMap;
44 import java.util.concurrent.atomic.AtomicLong;
45 
46 /**
47  * LRU cache that's invalidated when an opaque value in a property changes. Self-synchronizing,
48  * but doesn't hold a lock across data fetches on query misses.
49  *
50  * The intended use case is caching frequently-read, seldom-changed information normally
51  * retrieved across interprocess communication. Imagine that you've written a user birthday
52  * information daemon called "birthdayd" that exposes an {@code IUserBirthdayService} interface
53  * over binder. That binder interface looks something like this:
54  *
55  * <pre>
56  * parcelable Birthday {
57  *   int month;
58  *   int day;
59  * }
60  * interface IUserBirthdayService {
61  *   Birthday getUserBirthday(int userId);
62  * }
63  * </pre>
64  *
65  * Suppose the service implementation itself looks like this...
66  *
67  * <pre>
68  * public class UserBirthdayServiceImpl implements IUserBirthdayService {
69  *   private final HashMap<Integer, Birthday> mUidToBirthday;
70  *   @Override
71  *   public synchronized Birthday getUserBirthday(int userId) {
72  *     return mUidToBirthday.get(userId);
73  *   }
74  *   private synchronized void updateBirthdays(Map<Integer, Birthday> uidToBirthday) {
75  *     mUidToBirthday.clear();
76  *     mUidToBirthday.putAll(uidToBirthday);
77  *   }
78  * }
79  * </pre>
80  *
81  * ... and we have a client in frameworks (loaded into every app process) that looks
82  * like this:
83  *
84  * <pre>
85  * public class ActivityThread {
86  *   ...
87  *   public Birthday getUserBirthday(int userId) {
88  *     return GetService("birthdayd").getUserBirthday(userId);
89  *   }
90  *   ...
91  * }
92  * </pre>
93  *
94  * With this code, every time an app calls {@code getUserBirthday(uid)}, we make a binder call
95  * to the birthdayd process and consult its database of birthdays. If we query user birthdays
96  * frequently, we do a lot of work that we don't have to do, since user birthdays
97  * change infrequently.
98  *
99  * PropertyInvalidatedCache is part of a pattern for optimizing this kind of
100  * information-querying code. Using {@code PropertyInvalidatedCache}, you'd write the client
101  * this way:
102  *
103  * <pre>
104  * public class ActivityThread {
105  *   ...
106  *   private static final int BDAY_CACHE_MAX = 8;  // Maximum birthdays to cache
107  *   private static final String BDAY_CACHE_KEY = "cache_key.birthdayd";
108  *   private final PropertyInvalidatedCache<Integer, Birthday> mBirthdayCache = new
109  *     PropertyInvalidatedCache<Integer, Birthday>(BDAY_CACHE_MAX, BDAY_CACHE_KEY) {
110  *       @Override
111  *       protected Birthday recompute(Integer userId) {
112  *         return GetService("birthdayd").getUserBirthday(userId);
113  *       }
114  *     };
115  *   public void disableUserBirthdayCache() {
116  *     mBirthdayCache.disableLocal();
117  *   }
118  *   public void invalidateUserBirthdayCache() {
119  *     mBirthdayCache.invalidateCache();
120  *   }
121  *   public Birthday getUserBirthday(int userId) {
122  *     return mBirthdayCache.query(userId);
123  *   }
124  *   ...
125  * }
126  * </pre>
127  *
128  * With this cache, clients perform a binder call to birthdayd if asking for a user's birthday
129  * for the first time; on subsequent queries, we return the already-known Birthday object.
130  *
131  * User birthdays do occasionally change, so we have to modify the server to invalidate this
132  * cache when necessary. That invalidation code looks like this:
133  *
134  * <pre>
135  * public class UserBirthdayServiceImpl {
136  *   ...
137  *   public UserBirthdayServiceImpl() {
138  *     ...
139  *     ActivityThread.currentActivityThread().disableUserBirthdayCache();
140  *     ActivityThread.currentActivityThread().invalidateUserBirthdayCache();
141  *   }
142  *
143  *   private synchronized void updateBirthdays(Map<Integer, Birthday> uidToBirthday) {
144  *     mUidToBirthday.clear();
145  *     mUidToBirthday.putAll(uidToBirthday);
146  *     ActivityThread.currentActivityThread().invalidateUserBirthdayCache();
147  *   }
148  *   ...
149  * }
150  * </pre>
151  *
152  * The call to {@code PropertyInvalidatedCache.invalidateCache()} guarantees that all clients
153  * will re-fetch birthdays from binder during consequent calls to
154  * {@code ActivityThread.getUserBirthday()}. Because the invalidate call happens with the lock
155  * held, we maintain consistency between different client views of the birthday state. The use
156  * of PropertyInvalidatedCache in this idiomatic way introduces no new race conditions.
157  *
158  * PropertyInvalidatedCache has a few other features for doing things like incremental
159  * enhancement of cached values and invalidation of multiple caches (that all share the same
160  * property key) at once.
161  *
162  * {@code BDAY_CACHE_KEY} is the name of a property that we set to an opaque unique value each
163  * time we update the cache. SELinux configuration must allow everyone to read this property
164  * and it must allow any process that needs to invalidate the cache (here, birthdayd) to write
165  * the property. (These properties conventionally begin with the "cache_key." prefix.)
166  *
167  * The {@code UserBirthdayServiceImpl} constructor calls {@code disableUserBirthdayCache()} so
168  * that calls to {@code getUserBirthday} from inside birthdayd don't go through the cache. In
169  * this local case, there's no IPC, so use of the cache is (depending on exact
170  * circumstance) unnecessary.
171  *
172  * For security, there is a allowlist of processes that are allowed to invalidate a cache.
173  * The allowlist includes normal runtime processes but does not include test processes.
174  * Test processes must call {@code PropertyInvalidatedCache.disableForTestMode()} to disable
175  * all cache activity in that process.
176  *
177  * Caching can be disabled completely by initializing {@code sEnabled} to false and rebuilding.
178  *
179  * To test a binder cache, create one or more tests that exercise the binder method.  This
180  * should be done twice: once with production code and once with a special image that sets
181  * {@code DEBUG} and {@code VERIFY} true.  In the latter case, verify that no cache
182  * inconsistencies are reported.  If a cache inconsistency is reported, however, it might be a
183  * false positive.  This happens if the server side data can be read and written non-atomically
184  * with respect to cache invalidation.
185  *
186  * @param <Query> The class used to index cache entries: must be hashable and comparable
187  * @param <Result> The class holding cache entries; use a boxed primitive if possible
188  *
189  * {@hide}
190  */
191 public abstract class PropertyInvalidatedCache<Query, Result> {
192     /**
193      * Reserved nonce values.  The code is written assuming that these
194      * values are contiguous.
195      */
196     private static final int NONCE_UNSET = 0;
197     private static final int NONCE_DISABLED = 1;
198     private static final int NONCE_CORKED = 2;
199     private static final int NONCE_RESERVED = NONCE_CORKED + 1;
200 
201     /**
202      * The names of the nonces
203      */
204     private static final String[] sNonceName =
205             new String[]{ "unset", "disabled", "corked" };
206 
207     private static final String TAG = "PropertyInvalidatedCache";
208     private static final boolean DEBUG = false;
209     private static final boolean VERIFY = false;
210     // If this is true, dumpsys will dump the cache entries along with cache statistics.
211     // Most of the time this causes dumpsys to fail because the output stream is too
212     // large.  Only set it to true in development images.
213     private static final boolean DETAILED = false;
214 
215     // Per-Cache performance counters. As some cache instances are declared static,
216     @GuardedBy("mLock")
217     private long mHits = 0;
218 
219     @GuardedBy("mLock")
220     private long mMisses = 0;
221 
222     @GuardedBy("mLock")
223     private long mSkips[] = new long[]{ 0, 0, 0 };
224 
225     @GuardedBy("mLock")
226     private long mMissOverflow = 0;
227 
228     @GuardedBy("mLock")
229     private long mHighWaterMark = 0;
230 
231     @GuardedBy("mLock")
232     private long mClears = 0;
233 
234     // Most invalidation is done in a static context, so the counters need to be accessible.
235     @GuardedBy("sCorkLock")
236     private static final HashMap<String, Long> sInvalidates = new HashMap<>();
237 
238     /**
239      * Record the number of invalidate or cork calls that were nops because
240      * the cache was already corked.  This is static because invalidation is
241      * done in a static context.
242      */
243     @GuardedBy("sCorkLock")
244     private static final HashMap<String, Long> sCorkedInvalidates = new HashMap<>();
245 
246     /**
247      * If sEnabled is false then all cache operations are stubbed out.  Set
248      * it to false inside test processes.
249      */
250     private static boolean sEnabled = true;
251 
252     private static final Object sCorkLock = new Object();
253 
254     /**
255      * A map of cache keys that we've "corked". (The values are counts.)  When a cache key is
256      * corked, we skip the cache invalidate when the cache key is in the unset state --- that
257      * is, when a cache key is corked, an invalidation does not enable the cache if somebody
258      * else hasn't disabled it.
259      */
260     @GuardedBy("sCorkLock")
261     private static final HashMap<String, Integer> sCorks = new HashMap<>();
262 
263     /**
264      * A map of cache keys that have been disabled in the local process.  When a key is
265      * disabled locally, existing caches are disabled and the key is saved in this map.
266      * Future cache instances that use the same key will be disabled in their constructor.
267      */
268     @GuardedBy("sCorkLock")
269     private static final HashSet<String> sDisabledKeys = new HashSet<>();
270 
271     /**
272      * Weakly references all cache objects in the current process, allowing us to iterate over
273      * them all for purposes like issuing debug dumps and reacting to memory pressure.
274      */
275     @GuardedBy("sCorkLock")
276     private static final WeakHashMap<PropertyInvalidatedCache, Void> sCaches = new WeakHashMap<>();
277 
278     private final Object mLock = new Object();
279 
280     /**
281      * Name of the property that holds the unique value that we use to invalidate the cache.
282      */
283     private final String mPropertyName;
284 
285     /**
286      * Handle to the {@code mPropertyName} property, transitioning to non-{@code null} once the
287      * property exists on the system.
288      */
289     private volatile SystemProperties.Handle mPropertyHandle;
290 
291     /**
292      * The name by which this cache is known.  This should normally be the
293      * binder call that is being cached, but the constructors default it to
294      * the property name.
295      */
296     private final String mCacheName;
297 
298     @GuardedBy("mLock")
299     private final LinkedHashMap<Query, Result> mCache;
300 
301     /**
302      * The last value of the {@code mPropertyHandle} that we observed.
303      */
304     @GuardedBy("mLock")
305     private long mLastSeenNonce = NONCE_UNSET;
306 
307     /**
308      * Whether we've disabled the cache in this process.
309      */
310     private boolean mDisabled = false;
311 
312     /**
313      * Maximum number of entries the cache will maintain.
314      */
315     private final int mMaxEntries;
316 
317     /**
318      * Make a new property invalidated cache.
319      *
320      * @param maxEntries Maximum number of entries to cache; LRU discard
321      * @param propertyName Name of the system property holding the cache invalidation nonce
322      * Defaults the cache name to the property name.
323      */
PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName)324     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
325         this(maxEntries, propertyName, propertyName);
326     }
327 
328     /**
329      * Make a new property invalidated cache.
330      *
331      * @param maxEntries Maximum number of entries to cache; LRU discard
332      * @param propertyName Name of the system property holding the cache invalidation nonce
333      * @param cacheName Name of this cache in debug and dumpsys
334      */
PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName, @NonNull String cacheName)335     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
336             @NonNull String cacheName) {
337         mPropertyName = propertyName;
338         mCacheName = cacheName;
339         mMaxEntries = maxEntries;
340         mCache = new LinkedHashMap<Query, Result>(
341             2 /* start small */,
342             0.75f /* default load factor */,
343             true /* LRU access order */) {
344                 @Override
345                 protected boolean removeEldestEntry(Map.Entry eldest) {
346                     final int size = size();
347                     if (size > mHighWaterMark) {
348                         mHighWaterMark = size;
349                     }
350                     if (size > maxEntries) {
351                         mMissOverflow++;
352                         return true;
353                     }
354                     return false;
355                 }
356             };
357         synchronized (sCorkLock) {
358             sCaches.put(this, null);
359             if (sDisabledKeys.contains(mCacheName)) {
360                 disableInstance();
361             }
362         }
363     }
364 
365     /**
366      * Forget all cached values.
367      */
clear()368     public final void clear() {
369         synchronized (mLock) {
370             if (DEBUG) {
371                 Log.d(TAG, "clearing cache for " + mPropertyName);
372             }
373             mCache.clear();
374             mClears++;
375         }
376     }
377 
378     /**
379      * Fetch a result from scratch in case it's not in the cache at all.  Called unlocked: may
380      * block. If this function returns null, the result of the cache query is null. There is no
381      * "negative cache" in the query: we don't cache null results at all.
382      */
recompute(Query query)383     protected abstract Result recompute(Query query);
384 
385     /**
386      * Return true if the query should bypass the cache.  The default behavior is to
387      * always use the cache but the method can be overridden for a specific class.
388      */
bypass(Query query)389     protected boolean bypass(Query query) {
390         return false;
391     }
392 
393     /**
394      * Determines if a pair of responses are considered equal. Used to determine whether
395      * a cache is inadvertently returning stale results when VERIFY is set to true.
396      */
debugCompareQueryResults(Result cachedResult, Result fetchedResult)397     protected boolean debugCompareQueryResults(Result cachedResult, Result fetchedResult) {
398         // If a service crashes and returns a null result, the cached value remains valid.
399         if (fetchedResult != null) {
400             return Objects.equals(cachedResult, fetchedResult);
401         }
402         return true;
403     }
404 
405     /**
406      * Make result up-to-date on a cache hit.  Called unlocked;
407      * may block.
408      *
409      * Return either 1) oldResult itself (the same object, by reference equality), in which
410      * case we just return oldResult as the result of the cache query, 2) a new object, which
411      * replaces oldResult in the cache and which we return as the result of the cache query
412      * after performing another property read to make sure that the result hasn't changed in
413      * the meantime (if the nonce has changed in the meantime, we drop the cache and try the
414      * whole query again), or 3) null, which causes the old value to be removed from the cache
415      * and null to be returned as the result of the cache query.
416      */
refresh(Result oldResult, Query query)417     protected Result refresh(Result oldResult, Query query) {
418         return oldResult;
419     }
420 
getCurrentNonce()421     private long getCurrentNonce() {
422         SystemProperties.Handle handle = mPropertyHandle;
423         if (handle == null) {
424             handle = SystemProperties.find(mPropertyName);
425             if (handle == null) {
426                 return NONCE_UNSET;
427             }
428             mPropertyHandle = handle;
429         }
430         return handle.getLong(NONCE_UNSET);
431     }
432 
433     /**
434      * Disable the use of this cache in this process.
435      */
disableInstance()436     public final void disableInstance() {
437         synchronized (mLock) {
438             mDisabled = true;
439             clear();
440         }
441     }
442 
443     /**
444      * Disable the local use of all caches with the same name.  All currently registered caches
445      * using the key will be disabled now, and all future cache instances that use the key will be
446      * disabled in their constructor.
447      */
disableLocal(@onNull String name)448     public static final void disableLocal(@NonNull String name) {
449         synchronized (sCorkLock) {
450             sDisabledKeys.add(name);
451             for (PropertyInvalidatedCache cache : sCaches.keySet()) {
452                 if (name.equals(cache.mCacheName)) {
453                     cache.disableInstance();
454                 }
455             }
456         }
457     }
458 
459     /**
460      * Disable this cache in the current process, and all other caches that use the same
461      * property.
462      */
disableLocal()463     public final void disableLocal() {
464         disableLocal(mCacheName);
465     }
466 
467     /**
468      * Return whether the cache is disabled in this process.
469      */
isDisabledLocal()470     public final boolean isDisabledLocal() {
471         return mDisabled || !sEnabled;
472     }
473 
474     /**
475      * Get a value from the cache or recompute it.
476      */
query(Query query)477     public Result query(Query query) {
478         // Let access to mDisabled race: it's atomic anyway.
479         long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED;
480         for (;;) {
481             if (currentNonce == NONCE_DISABLED || currentNonce == NONCE_UNSET
482                     || currentNonce == NONCE_CORKED || bypass(query)) {
483                 if (!mDisabled) {
484                     // Do not bother collecting statistics if the cache is
485                     // locally disabled.
486                     synchronized (mLock) {
487                         mSkips[(int) currentNonce]++;
488                     }
489                 }
490 
491                 if (DEBUG) {
492                     if (!mDisabled) {
493                         Log.d(TAG, String.format(
494                             "cache %s %s for %s",
495                             cacheName(), sNonceName[(int) currentNonce], queryToString(query)));
496                     }
497                 }
498                 return recompute(query);
499             }
500             final Result cachedResult;
501             synchronized (mLock) {
502                 if (currentNonce == mLastSeenNonce) {
503                     cachedResult = mCache.get(query);
504 
505                     if (cachedResult != null) mHits++;
506                 } else {
507                     if (DEBUG) {
508                         Log.d(TAG, String.format(
509                             "clearing cache %s of %d entries because nonce changed [%s] -> [%s]",
510                             cacheName(), mCache.size(),
511                             mLastSeenNonce, currentNonce));
512                     }
513                     clear();
514                     mLastSeenNonce = currentNonce;
515                     cachedResult = null;
516                 }
517             }
518             // Cache hit --- but we're not quite done yet.  A value in the cache might need to
519             // be augmented in a "refresh" operation.  The refresh operation can combine the
520             // old and the new nonce values.  In order to make sure the new parts of the value
521             // are consistent with the old, possibly-reused parts, we check the property value
522             // again after the refresh and do the whole fetch again if the property invalidated
523             // us while we were refreshing.
524             if (cachedResult != null) {
525                 final Result refreshedResult = refresh(cachedResult, query);
526                 if (refreshedResult != cachedResult) {
527                     if (DEBUG) {
528                         Log.d(TAG, "cache refresh for " + cacheName() + " " + queryToString(query));
529                     }
530                     final long afterRefreshNonce = getCurrentNonce();
531                     if (currentNonce != afterRefreshNonce) {
532                         currentNonce = afterRefreshNonce;
533                         if (DEBUG) {
534                             Log.d(TAG, String.format("restarting %s %s because nonce changed in refresh",
535                                                      cacheName(),
536                                                      queryToString(query)));
537                         }
538                         continue;
539                     }
540                     synchronized (mLock) {
541                         if (currentNonce != mLastSeenNonce) {
542                             // Do nothing: cache is already out of date. Just return the value
543                             // we already have: there's no guarantee that the contents of mCache
544                             // won't become invalid as soon as we return.
545                         } else if (refreshedResult == null) {
546                             mCache.remove(query);
547                         } else {
548                             mCache.put(query, refreshedResult);
549                         }
550                     }
551                     return maybeCheckConsistency(query, refreshedResult);
552                 }
553                 if (DEBUG) {
554                     Log.d(TAG, "cache hit for " + cacheName() + " " + queryToString(query));
555                 }
556                 return maybeCheckConsistency(query, cachedResult);
557             }
558             // Cache miss: make the value from scratch.
559             if (DEBUG) {
560                 Log.d(TAG, "cache miss for " + cacheName() + " " + queryToString(query));
561             }
562             final Result result = recompute(query);
563             synchronized (mLock) {
564                 // If someone else invalidated the cache while we did the recomputation, don't
565                 // update the cache with a potentially stale result.
566                 if (mLastSeenNonce == currentNonce && result != null) {
567                     mCache.put(query, result);
568                 }
569                 mMisses++;
570             }
571             return maybeCheckConsistency(query, result);
572         }
573     }
574 
575     // Inner class avoids initialization in processes that don't do any invalidation
576     private static final class NoPreloadHolder {
577         private static final AtomicLong sNextNonce = new AtomicLong((new Random()).nextLong());
next()578         public static long next() {
579             return sNextNonce.getAndIncrement();
580         }
581     }
582 
583     /**
584      * Non-static convenience version of disableSystemWide() for situations in which only a
585      * single PropertyInvalidatedCache is keyed on a particular property value.
586      *
587      * When multiple caches share a single property value, using an instance method on one of
588      * the cache objects to invalidate all of the cache objects becomes confusing and you should
589      * just use the static version of this function.
590      */
disableSystemWide()591     public final void disableSystemWide() {
592         disableSystemWide(mPropertyName);
593     }
594 
595     /**
596      * Disable all caches system-wide that are keyed on {@var name}. This
597      * function is synchronous: caches are invalidated and disabled upon return.
598      *
599      * @param name Name of the cache-key property to invalidate
600      */
disableSystemWide(@onNull String name)601     public static void disableSystemWide(@NonNull String name) {
602         if (!sEnabled) {
603             return;
604         }
605         SystemProperties.set(name, Long.toString(NONCE_DISABLED));
606     }
607 
608     /**
609      * Non-static convenience version of invalidateCache() for situations in which only a single
610      * PropertyInvalidatedCache is keyed on a particular property value.
611      */
invalidateCache()612     public final void invalidateCache() {
613         invalidateCache(mPropertyName);
614     }
615 
616     /**
617      * Invalidate PropertyInvalidatedCache caches in all processes that are keyed on
618      * {@var name}. This function is synchronous: caches are invalidated upon return.
619      *
620      * @param name Name of the cache-key property to invalidate
621      */
invalidateCache(@onNull String name)622     public static void invalidateCache(@NonNull String name) {
623         if (!sEnabled) {
624             if (DEBUG) {
625                 Log.w(TAG, String.format(
626                     "cache invalidate %s suppressed", name));
627             }
628             return;
629         }
630 
631         // Take the cork lock so invalidateCache() racing against corkInvalidations() doesn't
632         // clobber a cork-written NONCE_UNSET with a cache key we compute before the cork.
633         // The property service is single-threaded anyway, so we don't lose any concurrency by
634         // taking the cork lock around cache invalidations.  If we see contention on this lock,
635         // we're invalidating too often.
636         synchronized (sCorkLock) {
637             Integer numberCorks = sCorks.get(name);
638             if (numberCorks != null && numberCorks > 0) {
639                 if (DEBUG) {
640                     Log.d(TAG, "ignoring invalidation due to cork: " + name);
641                 }
642                 final long count = sCorkedInvalidates.getOrDefault(name, (long) 0);
643                 sCorkedInvalidates.put(name, count + 1);
644                 return;
645             }
646             invalidateCacheLocked(name);
647         }
648     }
649 
650     @GuardedBy("sCorkLock")
invalidateCacheLocked(@onNull String name)651     private static void invalidateCacheLocked(@NonNull String name) {
652         // There's no race here: we don't require that values strictly increase, but instead
653         // only that each is unique in a single runtime-restart session.
654         final long nonce = SystemProperties.getLong(name, NONCE_UNSET);
655         if (nonce == NONCE_DISABLED) {
656             if (DEBUG) {
657                 Log.d(TAG, "refusing to invalidate disabled cache: " + name);
658             }
659             return;
660         }
661 
662         long newValue;
663         do {
664             newValue = NoPreloadHolder.next();
665         } while (newValue >= 0 && newValue < NONCE_RESERVED);
666         final String newValueString = Long.toString(newValue);
667         if (DEBUG) {
668             Log.d(TAG,
669                     String.format("invalidating cache [%s]: [%s] -> [%s]",
670                             name,
671                             nonce,
672                             newValueString));
673         }
674         // TODO(dancol): add an atomic compare and exchange property set operation to avoid a
675         // small race with concurrent disable here.
676         SystemProperties.set(name, newValueString);
677         long invalidateCount = sInvalidates.getOrDefault(name, (long) 0);
678         sInvalidates.put(name, ++invalidateCount);
679     }
680 
681     /**
682      * Temporarily put the cache in the uninitialized state and prevent invalidations from
683      * moving it out of that state: useful in cases where we want to avoid the overhead of a
684      * large number of cache invalidations in a short time.  While the cache is corked, clients
685      * bypass the cache and talk to backing services directly.  This property makes corking
686      * correctness-preserving even if corked outside the lock that controls access to the
687      * cache's backing service.
688      *
689      * corkInvalidations() and uncorkInvalidations() must be called in pairs.
690      *
691      * @param name Name of the cache-key property to cork
692      */
corkInvalidations(@onNull String name)693     public static void corkInvalidations(@NonNull String name) {
694         if (!sEnabled) {
695             if (DEBUG) {
696                 Log.w(TAG, String.format(
697                     "cache cork %s suppressed", name));
698             }
699             return;
700         }
701 
702         synchronized (sCorkLock) {
703             int numberCorks = sCorks.getOrDefault(name, 0);
704             if (DEBUG) {
705                 Log.d(TAG, String.format("corking %s: numberCorks=%s", name, numberCorks));
706             }
707 
708             // If we're the first ones to cork this cache, set the cache to the corked state so
709             // existing caches talk directly to their services while we've corked updates.
710             // Make sure we don't clobber a disabled cache value.
711 
712             // TODO(dancol): we can skip this property write and leave the cache enabled if the
713             // caller promises not to make observable changes to the cache backing state before
714             // uncorking the cache, e.g., by holding a read lock across the cork-uncork pair.
715             // Implement this more dangerous mode of operation if necessary.
716             if (numberCorks == 0) {
717                 final long nonce = SystemProperties.getLong(name, NONCE_UNSET);
718                 if (nonce != NONCE_UNSET && nonce != NONCE_DISABLED) {
719                     SystemProperties.set(name, Long.toString(NONCE_CORKED));
720                 }
721             } else {
722                 final long count = sCorkedInvalidates.getOrDefault(name, (long) 0);
723                 sCorkedInvalidates.put(name, count + 1);
724             }
725             sCorks.put(name, numberCorks + 1);
726             if (DEBUG) {
727                 Log.d(TAG, "corked: " + name);
728             }
729         }
730     }
731 
732     /**
733      * Undo the effect of a cork, allowing cache invalidations to proceed normally.
734      * Removing the last cork on a cache name invalidates the cache by side effect,
735      * transitioning it to normal operation (unless explicitly disabled system-wide).
736      *
737      * @param name Name of the cache-key property to uncork
738      */
uncorkInvalidations(@onNull String name)739     public static void uncorkInvalidations(@NonNull String name) {
740         if (!sEnabled) {
741             if (DEBUG) {
742                 Log.w(TAG, String.format(
743                     "cache uncork %s suppressed", name));
744             }
745             return;
746         }
747 
748         synchronized (sCorkLock) {
749             int numberCorks = sCorks.getOrDefault(name, 0);
750             if (DEBUG) {
751                 Log.d(TAG, String.format("uncorking %s: numberCorks=%s", name, numberCorks));
752             }
753 
754             if (numberCorks < 1) {
755                 throw new AssertionError("cork underflow: " + name);
756             }
757             if (numberCorks == 1) {
758                 sCorks.remove(name);
759                 invalidateCacheLocked(name);
760                 if (DEBUG) {
761                     Log.d(TAG, "uncorked: " + name);
762                 }
763             } else {
764                 sCorks.put(name, numberCorks - 1);
765             }
766         }
767     }
768 
769     /**
770      * Time-based automatic corking helper. This class allows providers of cached data to
771      * amortize the cost of cache invalidations by corking the cache immediately after a
772      * modification (instructing clients to bypass the cache temporarily) and automatically
773      * uncork after some period of time has elapsed.
774      *
775      * It's better to use explicit cork and uncork pairs that tighly surround big batches of
776      * invalidations, but it's not always practical to tell where these invalidation batches
777      * might occur. AutoCorker's time-based corking is a decent alternative.
778      *
779      * The auto-cork delay is configurable but it should not be too long.  The purpose of
780      * the delay is to minimize the number of times a server writes to the system property
781      * when invalidating the cache.  One write every 50ms does not hurt system performance.
782      */
783     public static final class AutoCorker {
784         public static final int DEFAULT_AUTO_CORK_DELAY_MS = 50;
785 
786         private final String mPropertyName;
787         private final int mAutoCorkDelayMs;
788         private final Object mLock = new Object();
789         @GuardedBy("mLock")
790         private long mUncorkDeadlineMs = -1;  // SystemClock.uptimeMillis()
791         @GuardedBy("mLock")
792         private Handler mHandler;
793 
AutoCorker(@onNull String propertyName)794         public AutoCorker(@NonNull String propertyName) {
795             this(propertyName, DEFAULT_AUTO_CORK_DELAY_MS);
796         }
797 
AutoCorker(@onNull String propertyName, int autoCorkDelayMs)798         public AutoCorker(@NonNull String propertyName, int autoCorkDelayMs) {
799             mPropertyName = propertyName;
800             mAutoCorkDelayMs = autoCorkDelayMs;
801             // We can't initialize mHandler here: when we're created, the main loop might not
802             // be set up yet! Wait until we have a main loop to initialize our
803             // corking callback.
804         }
805 
autoCork()806         public void autoCork() {
807             if (Looper.getMainLooper() == null) {
808                 // We're not ready to auto-cork yet, so just invalidate the cache immediately.
809                 if (DEBUG) {
810                     Log.w(TAG, "invalidating instead of autocorking early in init: "
811                             + mPropertyName);
812                 }
813                 PropertyInvalidatedCache.invalidateCache(mPropertyName);
814                 return;
815             }
816             synchronized (mLock) {
817                 boolean alreadyQueued = mUncorkDeadlineMs >= 0;
818                 if (DEBUG) {
819                     Log.w(TAG, String.format(
820                             "autoCork %s mUncorkDeadlineMs=%s", mPropertyName,
821                             mUncorkDeadlineMs));
822                 }
823                 mUncorkDeadlineMs = SystemClock.uptimeMillis() + mAutoCorkDelayMs;
824                 if (!alreadyQueued) {
825                     getHandlerLocked().sendEmptyMessageAtTime(0, mUncorkDeadlineMs);
826                     PropertyInvalidatedCache.corkInvalidations(mPropertyName);
827                 } else {
828                     final long count = sCorkedInvalidates.getOrDefault(mPropertyName, (long) 0);
829                     sCorkedInvalidates.put(mPropertyName, count + 1);
830                 }
831             }
832         }
833 
handleMessage(Message msg)834         private void handleMessage(Message msg) {
835             synchronized (mLock) {
836                 if (DEBUG) {
837                     Log.w(TAG, String.format(
838                             "handleMsesage %s mUncorkDeadlineMs=%s",
839                             mPropertyName, mUncorkDeadlineMs));
840                 }
841 
842                 if (mUncorkDeadlineMs < 0) {
843                     return;  // ???
844                 }
845                 long nowMs = SystemClock.uptimeMillis();
846                 if (mUncorkDeadlineMs > nowMs) {
847                     mUncorkDeadlineMs = nowMs + mAutoCorkDelayMs;
848                     if (DEBUG) {
849                         Log.w(TAG, String.format(
850                                         "scheduling uncork at %s",
851                                         mUncorkDeadlineMs));
852                     }
853                     getHandlerLocked().sendEmptyMessageAtTime(0, mUncorkDeadlineMs);
854                     return;
855                 }
856                 if (DEBUG) {
857                     Log.w(TAG, "automatic uncorking " + mPropertyName);
858                 }
859                 mUncorkDeadlineMs = -1;
860                 PropertyInvalidatedCache.uncorkInvalidations(mPropertyName);
861             }
862         }
863 
864         @GuardedBy("mLock")
getHandlerLocked()865         private Handler getHandlerLocked() {
866             if (mHandler == null) {
867                 mHandler = new Handler(Looper.getMainLooper()) {
868                         @Override
869                         public void handleMessage(Message msg) {
870                             AutoCorker.this.handleMessage(msg);
871                         }
872                     };
873             }
874             return mHandler;
875         }
876     }
877 
maybeCheckConsistency(Query query, Result proposedResult)878     protected Result maybeCheckConsistency(Query query, Result proposedResult) {
879         if (VERIFY) {
880             Result resultToCompare = recompute(query);
881             boolean nonceChanged = (getCurrentNonce() != mLastSeenNonce);
882             if (!nonceChanged && !debugCompareQueryResults(proposedResult, resultToCompare)) {
883                 Log.e(TAG, String.format(
884                     "cache %s inconsistent for %s is %s should be %s",
885                     cacheName(), queryToString(query),
886                     proposedResult, resultToCompare));
887             }
888             // Always return the "true" result in verification mode.
889             return resultToCompare;
890         }
891         return proposedResult;
892     }
893 
894     /**
895      * Return the name of the cache, to be used in debug messages.  The
896      * method is public so clients can use it.
897      */
cacheName()898     public String cacheName() {
899         return mCacheName;
900     }
901 
902     /**
903      * Return the query as a string, to be used in debug messages.  The
904      * method is public so clients can use it in external debug messages.
905      */
queryToString(Query query)906     public String queryToString(Query query) {
907         return Objects.toString(query);
908     }
909 
910     /**
911      * Disable all caches in the local process.  Once disabled it is not
912      * possible to re-enable caching in the current process.
913      */
914     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
disableForTestMode()915     public static void disableForTestMode() {
916         Log.d(TAG, "disabling all caches in the process");
917         sEnabled = false;
918     }
919 
920     /**
921      * Report the disabled status of this cache instance.  The return value does not
922      * reflect status of the property key.
923      */
924     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
getDisabledState()925     public boolean getDisabledState() {
926         return isDisabledLocal();
927     }
928 
929     /**
930      * Returns a list of caches alive at the current time.
931      */
getActiveCaches()932     public static ArrayList<PropertyInvalidatedCache> getActiveCaches() {
933         synchronized (sCorkLock) {
934             return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet());
935         }
936     }
937 
938     /**
939      * Returns a list of the active corks in a process.
940      */
getActiveCorks()941     public static ArrayList<Map.Entry<String, Integer>> getActiveCorks() {
942         synchronized (sCorkLock) {
943             return new ArrayList<Map.Entry<String, Integer>>(sCorks.entrySet());
944         }
945     }
946 
dumpContents(PrintWriter pw, String[] args)947     private void dumpContents(PrintWriter pw, String[] args) {
948         long invalidateCount;
949         long corkedInvalidates;
950         synchronized (sCorkLock) {
951             invalidateCount = sInvalidates.getOrDefault(mPropertyName, (long) 0);
952             corkedInvalidates = sCorkedInvalidates.getOrDefault(mPropertyName, (long) 0);
953         }
954 
955         synchronized (mLock) {
956             pw.println(String.format("  Cache Name: %s", cacheName()));
957             pw.println(String.format("    Property: %s", mPropertyName));
958             final long skips = mSkips[NONCE_CORKED] + mSkips[NONCE_UNSET] + mSkips[NONCE_DISABLED];
959             pw.println(String.format("    Hits: %d, Misses: %d, Skips: %d, Clears: %d",
960                     mHits, mMisses, skips, mClears));
961             pw.println(String.format("    Skip-corked: %d, Skip-unset: %d, Skip-other: %d",
962                     mSkips[NONCE_CORKED], mSkips[NONCE_UNSET],
963                     mSkips[NONCE_DISABLED]));
964             pw.println(String.format(
965                     "    Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d",
966                     mLastSeenNonce, invalidateCount, corkedInvalidates));
967             pw.println(String.format(
968                     "    Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
969                     mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow));
970             pw.println(String.format("    Enabled: %s", mDisabled ? "false" : "true"));
971             pw.println("");
972 
973             Set<Map.Entry<Query, Result>> cacheEntries = mCache.entrySet();
974             if (!DETAILED || cacheEntries.size() == 0) {
975                 return;
976             }
977 
978             pw.println("    Contents:");
979             for (Map.Entry<Query, Result> entry : cacheEntries) {
980                 String key = Objects.toString(entry.getKey());
981                 String value = Objects.toString(entry.getValue());
982 
983                 pw.println(String.format("      Key: %s\n      Value: %s\n", key, value));
984             }
985         }
986     }
987 
988     /**
989      * Dumps contents of every cache in the process to the provided FileDescriptor.
990      */
dumpCacheInfo(FileDescriptor fd, String[] args)991     public static void dumpCacheInfo(FileDescriptor fd, String[] args) {
992         ArrayList<PropertyInvalidatedCache> activeCaches;
993         ArrayList<Map.Entry<String, Integer>> activeCorks;
994 
995         try  (
996             FileOutputStream fout = new FileOutputStream(fd);
997             PrintWriter pw = new FastPrintWriter(fout);
998         ) {
999             if (!sEnabled) {
1000                 pw.println("  Caching is disabled in this process.");
1001                 return;
1002             }
1003 
1004             synchronized (sCorkLock) {
1005                 activeCaches = getActiveCaches();
1006                 activeCorks = getActiveCorks();
1007 
1008                 if (activeCorks.size() > 0) {
1009                     pw.println("  Corking Status:");
1010                     for (int i = 0; i < activeCorks.size(); i++) {
1011                         Map.Entry<String, Integer> entry = activeCorks.get(i);
1012                         pw.println(String.format("    Property Name: %s Count: %d",
1013                                 entry.getKey(), entry.getValue()));
1014                     }
1015                 }
1016             }
1017 
1018             for (int i = 0; i < activeCaches.size(); i++) {
1019                 PropertyInvalidatedCache currentCache = activeCaches.get(i);
1020                 currentCache.dumpContents(pw, args);
1021                 pw.flush();
1022             }
1023         } catch (IOException e) {
1024             Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances");
1025         }
1026     }
1027 }
1028