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