diff options
| author | Daniel Colascione <dancol@google.com> | 2020-03-19 13:56:35 -0700 |
|---|---|---|
| committer | Daniel Colascione <dancol@google.com> | 2020-03-20 17:06:33 -0700 |
| commit | bbba8a867c5f95266fed84eb05548baef6ec9705 (patch) | |
| tree | 0a60dd9e609c503900bde5ae3c931c097cb58ae1 /core/java/android/app/PropertyInvalidatedCache.java | |
| parent | 381a9fdca052b10a21a655a7727db30133d7f4d7 (diff) | |
Add a facility for time-based cache corking
AutoCorker addresses the situation where big invalidation storms kill
performance but we don't have a way to insert a manual cork around
these update storms.
Bug: 140788621
Test: m
Change-Id: If07d693886fca340c7a18d5a607a4f235aa7107d
Diffstat (limited to 'core/java/android/app/PropertyInvalidatedCache.java')
| -rw-r--r-- | core/java/android/app/PropertyInvalidatedCache.java | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index a38b1aa7dbee..3d9b08d1b2f1 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -17,6 +17,10 @@ package android.app; import android.annotation.NonNull; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; import android.os.SystemProperties; import android.util.Log; @@ -496,6 +500,10 @@ public abstract class PropertyInvalidatedCache<Query, Result> { public static void corkInvalidations(@NonNull String name) { synchronized (sCorkLock) { int numberCorks = sCorks.getOrDefault(name, 0); + if (DEBUG) { + Log.d(TAG, String.format("corking %s: numberCorks=%s", name, numberCorks)); + } + // If we're the first ones to cork this cache, set the cache to the unset state so // existing caches talk directly to their services while we've corked updates. // Make sure we don't clobber a disabled cache value. @@ -527,6 +535,10 @@ public abstract class PropertyInvalidatedCache<Query, Result> { public static void uncorkInvalidations(@NonNull String name) { synchronized (sCorkLock) { int numberCorks = sCorks.getOrDefault(name, 0); + if (DEBUG) { + Log.d(TAG, String.format("uncorking %s: numberCorks=%s", name, numberCorks)); + } + if (numberCorks < 1) { throw new AssertionError("cork underflow: " + name); } @@ -542,6 +554,106 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } } + /** + * Time-based automatic corking helper. This class allows providers of cached data to + * amortize the cost of cache invalidations by corking the cache immediately after a + * modification (instructing clients to bypass the cache temporarily) and automatically + * uncork after some period of time has elapsed. + * + * It's better to use explicit cork and uncork pairs that tighly surround big batches of + * invalidations, but it's not always practical to tell where these invalidation batches + * might occur. AutoCorker's time-based corking is a decent alternative. + */ + public static final class AutoCorker { + public static final int DEFAULT_AUTO_CORK_DELAY_MS = 2000; + + private final String mPropertyName; + private final int mAutoCorkDelayMs; + private final Object mLock = new Object(); + @GuardedBy("mLock") + private long mUncorkDeadlineMs = -1; // SystemClock.uptimeMillis() + @GuardedBy("mLock") + private Handler mHandler; + + public AutoCorker(@NonNull String propertyName) { + this(propertyName, DEFAULT_AUTO_CORK_DELAY_MS); + } + + public AutoCorker(@NonNull String propertyName, int autoCorkDelayMs) { + mPropertyName = propertyName; + mAutoCorkDelayMs = autoCorkDelayMs; + // We can't initialize mHandler here: when we're created, the main loop might not + // be set up yet! Wait until we have a main loop to initialize our + // corking callback. + } + + public void autoCork() { + if (Looper.getMainLooper() == null) { + // We're not ready to auto-cork yet, so just invalidate the cache immediately. + if (DEBUG) { + Log.w(TAG, "invalidating instead of autocorking early in init: " + + mPropertyName); + } + PropertyInvalidatedCache.invalidateCache(mPropertyName); + return; + } + synchronized (mLock) { + boolean alreadyQueued = mUncorkDeadlineMs >= 0; + if (DEBUG) { + Log.w(TAG, String.format( + "autoCork mUncorkDeadlineMs=%s", mUncorkDeadlineMs)); + } + mUncorkDeadlineMs = SystemClock.uptimeMillis() + mAutoCorkDelayMs; + if (!alreadyQueued) { + getHandlerLocked().sendEmptyMessageAtTime(0, mUncorkDeadlineMs); + PropertyInvalidatedCache.corkInvalidations(mPropertyName); + } + } + } + + private void handleMessage(Message msg) { + synchronized (mLock) { + if (DEBUG) { + Log.w(TAG, String.format( + "handleMsesage mUncorkDeadlineMs=%s", mUncorkDeadlineMs)); + } + + if (mUncorkDeadlineMs < 0) { + return; // ??? + } + long nowMs = SystemClock.uptimeMillis(); + if (mUncorkDeadlineMs > nowMs) { + mUncorkDeadlineMs = nowMs + mAutoCorkDelayMs; + if (DEBUG) { + Log.w(TAG, String.format( + "scheduling uncork at %s", + mUncorkDeadlineMs)); + } + getHandlerLocked().sendEmptyMessageAtTime(0, mUncorkDeadlineMs); + return; + } + if (DEBUG) { + Log.w(TAG, "automatic uncorking " + mPropertyName); + } + mUncorkDeadlineMs = -1; + PropertyInvalidatedCache.uncorkInvalidations(mPropertyName); + } + } + + @GuardedBy("mLock") + private Handler getHandlerLocked() { + if (mHandler == null) { + mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + AutoCorker.this.handleMessage(msg); + } + }; + } + return mHandler; + } + } + protected Result maybeCheckConsistency(Query query, Result proposedResult) { if (VERIFY) { Result resultToCompare = recompute(query); |
