diff options
| author | 2020-06-18 00:32:23 -0700 | |
|---|---|---|
| committer | 2020-06-22 14:22:32 -0700 | |
| commit | 6985fb39f07294fb979b14ba0ebabfd2fea06d34 (patch) | |
| tree | 749c27d7b1eca972313dab30bbccf2f8536fcd26 | |
| parent | 26dfdc587fcaef5f68187b6bf1343fd7e2914d32 (diff) | |
Fix memory leak in StrictMode violation throttling
* Add hashCode() to the base Violation class for dup detections
* Discard obsoleted violation fingerprints if possible
Bug: 159128771
Bug: 159626227
Test: run cts -m CtsOsTestCases -t android.os.cts.StrictModeTest
Test: Manual - Loop generation of StrictMode violations
Test: Manual - Check heap dump
Change-Id: I19a0922fe010fad97b6b819e73acb7db08f84930
| -rw-r--r-- | core/java/android/os/StrictMode.java | 63 | ||||
| -rw-r--r-- | core/java/android/os/strictmode/Violation.java | 51 |
2 files changed, 98 insertions, 16 deletions
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 772845d4e683..8d65c92db52e 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -84,6 +84,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; @@ -189,6 +191,9 @@ public final class StrictMode { // Only show an annoying dialog at most every 30 seconds private static final long MIN_DIALOG_INTERVAL_MS = 30000; + // Only log a dropbox entry at most every 30 seconds + private static final long MIN_DROPBOX_INTERVAL_MS = 3000; + // How many Span tags (e.g. animations) to report. private static final int MAX_SPAN_TAGS = 20; @@ -1752,16 +1757,20 @@ public final class StrictMode { // Not perfect, but fast and good enough for dup suppression. Integer crashFingerprint = info.hashCode(); long lastViolationTime = 0; - if (mLastViolationTime != null) { - Long vtime = mLastViolationTime.get(crashFingerprint); - if (vtime != null) { - lastViolationTime = vtime; + long now = SystemClock.uptimeMillis(); + if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger + if (mLastViolationTime != null) { + Long vtime = mLastViolationTime.get(crashFingerprint); + if (vtime != null) { + lastViolationTime = vtime; + } + clampViolationTimeMap(mLastViolationTime, Math.max(MIN_LOG_INTERVAL_MS, + Math.max(MIN_DIALOG_INTERVAL_MS, MIN_DROPBOX_INTERVAL_MS))); + } else { + mLastViolationTime = new ArrayMap<>(1); } - } else { - mLastViolationTime = new ArrayMap<>(1); + mLastViolationTime.put(crashFingerprint, now); } - long now = SystemClock.uptimeMillis(); - mLastViolationTime.put(crashFingerprint, now); long timeSinceLastViolationMillis = lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime); @@ -1780,7 +1789,8 @@ public final class StrictMode { penaltyMask |= PENALTY_DIALOG; } - if (info.penaltyEnabled(PENALTY_DROPBOX) && lastViolationTime == 0) { + if (info.penaltyEnabled(PENALTY_DROPBOX) + && timeSinceLastViolationMillis > MIN_DROPBOX_INTERVAL_MS) { penaltyMask |= PENALTY_DROPBOX; } @@ -2215,6 +2225,23 @@ public final class StrictMode { @UnsupportedAppUsage private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<>(); + /** + * Clamp the given map by removing elements with timestamp older than the given retainSince. + */ + private static void clampViolationTimeMap(final @NonNull Map<Integer, Long> violationTime, + final long retainSince) { + final Iterator<Map.Entry<Integer, Long>> iterator = violationTime.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry<Integer, Long> e = iterator.next(); + if (e.getValue() < retainSince) { + // Remove stale entries + iterator.remove(); + } + } + // Ideally we'd cap the total size of the map, though it'll involve quickselect of topK, + // seems not worth it (saving some space immediately but they will be obsoleted soon anyway) + } + /** @hide */ public static void onVmPolicyViolation(Violation originStack) { onVmPolicyViolation(originStack, false); @@ -2238,13 +2265,17 @@ public final class StrictMode { final long now = SystemClock.uptimeMillis(); long lastViolationTime; long timeSinceLastViolationMillis = Long.MAX_VALUE; - synchronized (sLastVmViolationTime) { - if (sLastVmViolationTime.containsKey(fingerprint)) { - lastViolationTime = sLastVmViolationTime.get(fingerprint); - timeSinceLastViolationMillis = now - lastViolationTime; - } - if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) { - sLastVmViolationTime.put(fingerprint, now); + if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger + synchronized (sLastVmViolationTime) { + if (sLastVmViolationTime.containsKey(fingerprint)) { + lastViolationTime = sLastVmViolationTime.get(fingerprint); + timeSinceLastViolationMillis = now - lastViolationTime; + } + if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) { + sLastVmViolationTime.put(fingerprint, now); + } + clampViolationTimeMap(sLastVmViolationTime, + now - Math.max(MIN_VM_INTERVAL_MS, MIN_LOG_INTERVAL_MS)); } } if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) { diff --git a/core/java/android/os/strictmode/Violation.java b/core/java/android/os/strictmode/Violation.java index 31c7d584fd65..0edb78a64243 100644 --- a/core/java/android/os/strictmode/Violation.java +++ b/core/java/android/os/strictmode/Violation.java @@ -18,7 +18,58 @@ package android.os.strictmode; /** Root class for all StrictMode violations. */ public abstract class Violation extends Throwable { + private int mHashCode; + private boolean mHashCodeValid; + Violation(String message) { super(message); } + + @Override + public int hashCode() { + synchronized (this) { + if (mHashCodeValid) { + return mHashCode; + } + final String message = getMessage(); + final Throwable cause = getCause(); + int hashCode = message != null ? message.hashCode() : getClass().hashCode(); + hashCode = hashCode * 37 + calcStackTraceHashCode(getStackTrace()); + hashCode = hashCode * 37 + (cause != null ? cause.toString().hashCode() : 0); + mHashCodeValid = true; + return mHashCode = hashCode; + } + } + + @Override + public synchronized Throwable initCause(Throwable cause) { + mHashCodeValid = false; + return super.initCause(cause); + } + + @Override + public void setStackTrace(StackTraceElement[] stackTrace) { + super.setStackTrace(stackTrace); + synchronized (this) { + mHashCodeValid = false; + } + } + + @Override + public synchronized Throwable fillInStackTrace() { + mHashCodeValid = false; + return super.fillInStackTrace(); + } + + private static int calcStackTraceHashCode(final StackTraceElement[] stackTrace) { + int hashCode = 17; + if (stackTrace != null) { + for (int i = 0; i < stackTrace.length; i++) { + if (stackTrace[i] != null) { + hashCode = hashCode * 37 + stackTrace[i].hashCode(); + } + } + } + return hashCode; + } } |