From a26b3e18c8253f05d95fe62749f6257850a282d4 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 27 Oct 2026 12:59:22 +0100 Subject: [PATCH 1/2] fix(anr): Skip ANR profiling while a debugger is connected --- .../api/sentry-android-core.api | 1 + .../core/anr/AnrProfilingIntegration.java | 15 +++++++++ .../core/anr/AnrProfilingIntegrationTest.kt | 31 +++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index efd776cabb8..b1d0fbff0d9 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -736,6 +736,7 @@ public class io/sentry/android/core/anr/AnrProfilingIntegration : io/sentry/Inte public fun close ()V protected fun getProfileManager ()Lio/sentry/android/core/anr/AnrProfileManager; protected fun getState ()Lio/sentry/android/core/anr/AnrProfilingIntegration$MainThreadState; + protected fun isDebuggerConnected ()Z public fun onBackground ()V public fun onForeground ()V public fun register (Lio/sentry/IScopes;Lio/sentry/SentryOptions;)V diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java index 3cb0bea4b0f..38db383081b 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java @@ -2,6 +2,7 @@ import static io.sentry.util.IntegrationUtils.addIntegrationToSdkVersion; +import android.os.Debug; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; @@ -211,6 +212,15 @@ public void run() { @ApiStatus.Internal protected void checkMainThread(final @NotNull Thread mainThread) throws IOException { + if (isDebuggerConnected()) { + // The main thread can sit at a breakpoint for a long time, which is not an ANR. Reset the + // timestamp, so the paused time does not count against us after the debugger detaches. + mainThreadState = MainThreadState.IDLE; + sampled = false; + updater.run(); + return; + } + final long now = SystemClock.uptimeMillis(); final long diff = now - lastMainThreadExecutionTime; @@ -268,6 +278,11 @@ protected void checkMainThread(final @NotNull Thread mainThread) throws IOExcept } } + @ApiStatus.Internal + protected boolean isDebuggerConnected() { + return Debug.isDebuggerConnected(); + } + @TestOnly @NotNull protected MainThreadState getState() { diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/anr/AnrProfilingIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/anr/AnrProfilingIntegrationTest.kt index 2ae48fb3253..75bca72b080 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/anr/AnrProfilingIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/anr/AnrProfilingIntegrationTest.kt @@ -196,6 +196,37 @@ class AnrProfilingIntegrationTest { assertEquals(AnrProfilingIntegration.MAX_NUM_STACKS, integration.numCollectedStacks.get()) } + @Test + fun `debugger pauses are treated as idle`() { + val mainThread = Thread.currentThread() + SystemClock.setCurrentTimeMillis(1_000) + + val androidOptions = + SentryAndroidOptions().apply { + cacheDirPath = tmpDir.root.absolutePath + setLogger(mockLogger) + anrProfilingSampleRate = 1.0 + } + + var debuggerConnected = true + val integration = + object : AnrProfilingIntegration() { + override fun isDebuggerConnected(): Boolean = debuggerConnected + } + integration.register(mockScopes, androidOptions) + + SystemClock.setCurrentTimeMillis(20_000) + integration.checkMainThread(mainThread) + assertEquals(AnrProfilingIntegration.MainThreadState.IDLE, integration.state) + assertEquals(0, integration.numCollectedStacks.get()) + + // the time spent paused must not count towards an ANR once the debugger detaches + debuggerConnected = false + SystemClock.setCurrentTimeMillis(20_100) + integration.checkMainThread(mainThread) + assertEquals(AnrProfilingIntegration.MainThreadState.IDLE, integration.state) + } + @Test fun `background foreground transitions don't trigger an ANR`() { val mainThread = Thread.currentThread() From b494bcccf13b5bd1eec83ed585b490923dee655b Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 27 Oct 2026 13:19:31 +0100 Subject: [PATCH 2/2] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5886cb0c6d2..e85bcf1600f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Fixes + +- Skip ANR profiling while a debugger is connected ([#6389](https://github.com/getsentry/sentry-java/pull/6389)) + ### Dependencies - Bump Native SDK from v0.17.2 to v0.17.3 ([#6250](https://github.com/getsentry/sentry-java/pull/6250))