summaryrefslogtreecommitdiff
path: root/core/java/android/app/PropertyInvalidatedCache.java
diff options
context:
space:
mode:
authorDaniel Colascione <dancol@google.com>2020-03-19 13:56:35 -0700
committerDaniel Colascione <dancol@google.com>2020-03-20 17:06:33 -0700
commitbbba8a867c5f95266fed84eb05548baef6ec9705 (patch)
tree0a60dd9e609c503900bde5ae3c931c097cb58ae1 /core/java/android/app/PropertyInvalidatedCache.java
parent381a9fdca052b10a21a655a7727db30133d7f4d7 (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.java112
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);