Skip to content

Commit

Permalink
Merge f5b0a4e into 4eb4794
Browse files Browse the repository at this point in the history
  • Loading branch information
markushi authored Mar 6, 2023
2 parents 4eb4794 + f5b0a4e commit a195319
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import android.app.ActivityManager;
import android.content.Context;
import android.os.Debug;
import android.os.SystemClock;
import io.sentry.ILogger;
import io.sentry.SentryLevel;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;

Expand All @@ -22,17 +22,21 @@ final class ANRWatchDog extends Thread {
private final boolean reportInDebug;
private final ANRListener anrListener;
private final MainLooperHandler uiHandler;
/** the interval in which we check if there's an ANR, in ms */
private final long pollingIntervalMs = 500;

private final long timeoutIntervalMillis;
private final @NotNull ILogger logger;
private final AtomicLong tick = new AtomicLong(0);

private volatile long lastKnownActiveUiTimestampMs = 0;
private final AtomicBoolean reported = new AtomicBoolean(false);

private final @NotNull Context context;

@SuppressWarnings("UnnecessaryLambda")
private final Runnable ticker =
() -> {
tick.set(0);
lastKnownActiveUiTimestampMs = SystemClock.uptimeMillis();
reported.set(false);
};

Expand All @@ -53,29 +57,33 @@ final class ANRWatchDog extends Thread {
@NotNull ILogger logger,
@NotNull MainLooperHandler uiHandler,
final @NotNull Context context) {
super();
super("|ANR-WatchDog|");

this.reportInDebug = reportInDebug;
this.anrListener = listener;
this.timeoutIntervalMillis = timeoutIntervalMillis;
this.logger = logger;
this.uiHandler = uiHandler;
this.context = context;

if (timeoutIntervalMillis < (pollingIntervalMs * 2)) {
throw new IllegalArgumentException(
String.format(
"ANRWatchDog: timeoutIntervalMillis has to be at least %d ms",
pollingIntervalMs * 2));
}
}

@Override
public void run() {
setName("|ANR-WatchDog|");
// when the watchdog gets started, let's assume there's no ANR
ticker.run();

long interval = timeoutIntervalMillis;
while (!isInterrupted()) {
boolean needPost = tick.get() == 0;
tick.addAndGet(interval);
if (needPost) {
uiHandler.post(ticker);
}
uiHandler.post(ticker);

try {
Thread.sleep(interval);
Thread.sleep(pollingIntervalMs);
} catch (InterruptedException e) {
try {
Thread.currentThread().interrupt();
Expand All @@ -90,8 +98,10 @@ public void run() {
return;
}

final long unresponsiveDurationMs = SystemClock.uptimeMillis() - lastKnownActiveUiTimestampMs;

// If the main thread has not handled ticker, it is blocked. ANR.
if (tick.get() != 0 && !reported.get()) {
if (unresponsiveDurationMs > timeoutIntervalMillis) {
if (!reportInDebug && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) {
logger.log(
SentryLevel.DEBUG,
Expand All @@ -100,48 +110,42 @@ public void run() {
continue;
}

// we only raise an ANR event if the process is in ANR state.
// if ActivityManager is not available, we'll still be able to send ANRs
final ActivityManager am =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

if (am != null) {
List<ActivityManager.ProcessErrorStateInfo> processesInErrorState = null;
try {
// It can throw RuntimeException or OutOfMemoryError
processesInErrorState = am.getProcessesInErrorState();
} catch (Throwable e) {
logger.log(
SentryLevel.ERROR, "Error getting ActivityManager#getProcessesInErrorState.", e);
}
// if list is null, there's no process in ANR state.
if (processesInErrorState == null) {
continue;
}
boolean isAnr = false;
for (ActivityManager.ProcessErrorStateInfo item : processesInErrorState) {
if (item.condition == NOT_RESPONDING) {
isAnr = true;
break;
}
}
if (!isAnr) {
continue;
}
}
if (isProcessNotResponding() && reported.compareAndSet(false, true)) {
final String message =
"Application Not Responding for at least " + timeoutIntervalMillis + " ms.";

logger.log(SentryLevel.INFO, "Raising ANR");
final String message =
"Application Not Responding for at least " + timeoutIntervalMillis + " ms.";
final ApplicationNotResponding error =
new ApplicationNotResponding(message, uiHandler.getThread());
anrListener.onAppNotResponding(error);
}
}
}
}

final ApplicationNotResponding error =
new ApplicationNotResponding(message, uiHandler.getThread());
anrListener.onAppNotResponding(error);
interval = timeoutIntervalMillis;
private boolean isProcessNotResponding() {
// we only raise an ANR event if the process is in ANR state.
// if ActivityManager is not available, we'll still be able to send ANRs
final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

reported.set(true);
if (am != null) {
List<ActivityManager.ProcessErrorStateInfo> processesInErrorState = null;
try {
// It can throw RuntimeException or OutOfMemoryError
processesInErrorState = am.getProcessesInErrorState();
} catch (Throwable e) {
logger.log(SentryLevel.ERROR, "Error getting ActivityManager#getProcessesInErrorState.", e);
}
// if list is null, there's no process in ANR state.
if (processesInErrorState == null) {
return false;
}
for (ActivityManager.ProcessErrorStateInfo item : processesInErrorState) {
if (item.condition == NOT_RESPONDING) {
return true;
}
}
}
return false;
}

public interface ANRListener {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
import android.content.Context;
import io.sentry.Hint;
import io.sentry.IHub;
import io.sentry.ITransaction;
import io.sentry.Integration;
import io.sentry.SentryEvent;
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.Span;
import io.sentry.SpanStatus;
import io.sentry.exception.ExceptionMechanismException;
import io.sentry.hints.AbnormalExit;
import io.sentry.protocol.Mechanism;
Expand Down Expand Up @@ -95,9 +98,27 @@ void reportANR(
final SentryEvent event = new SentryEvent(anrThrowable);
event.setLevel(SentryLevel.ERROR);

// In case there's an ongoing transaction we want to finish it
// and assign the trace span context to the event
hub.configureScope(
scope -> {
@Nullable ITransaction transaction = scope.getTransaction();
if (transaction != null) {

for (Span span : transaction.getSpans()) {
span.finish(SpanStatus.ABORTED);
}
if (!transaction.isFinished()) {
transaction.finish(SpanStatus.ABORTED);
}

event.setTransaction(transaction.getName());
event.getContexts().setTrace(transaction.getSpanContext());
}
});

final AnrHint anrHint = new AnrHint(isAppInBackground);
final Hint hint = HintUtils.createWithTypeCheckHint(anrHint);

hub.captureEvent(event, hint);
}

Expand Down

0 comments on commit a195319

Please sign in to comment.