summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jing Ji <jji@google.com> 2020-06-18 00:32:23 -0700
committer Jing Ji <jji@google.com> 2020-06-22 14:22:32 -0700
commit6985fb39f07294fb979b14ba0ebabfd2fea06d34 (patch)
tree749c27d7b1eca972313dab30bbccf2f8536fcd26
parent26dfdc587fcaef5f68187b6bf1343fd7e2914d32 (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.java63
-rw-r--r--core/java/android/os/strictmode/Violation.java51
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;
+ }
}