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