From ade58ccc325dab68a93ae3b5acaa741334ef6a26 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 24 Jul 2026 17:38:18 +0200 Subject: [PATCH 1/2] fix(replay): Post checkCanRecord to main thread to prevent deadlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit onScreenshotRecorded can run on the replay executor thread (PixelCopy masked-capture and emit paths). checkCanRecord -> pauseInternal acquires lifecycleLock — if another thread holds that lock and submits to the same single-threaded executor, we deadlock. Post checkCanRecord to the main looper so it never runs on the executor. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../java/io/sentry/android/replay/ReplayIntegration.kt | 8 ++++++-- .../io/sentry/android/replay/ReplayIntegrationTest.kt | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt index d9cd15d891a..42d55215e23 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt @@ -352,7 +352,11 @@ public class ReplayIntegration( } addFrame(bitmap, frameTimeStamp, screen) } - checkCanRecord() + // Post to the main thread: onScreenshotRecorded can run on the replay executor + // (PixelCopy masked-capture and emit paths), and checkCanRecord -> pauseInternal + // acquires lifecycleLock — if another thread holds that lock while submitting to + // the same executor, we deadlock. + mainLooperHandler.post { checkCanRecord() } } override fun onScreenshotRecorded(screenshot: File, frameTimestamp: Long) { @@ -375,7 +379,7 @@ public class ReplayIntegration( } addFrame(screenshot, frameTimestamp, screen) } - checkCanRecord() + mainLooperHandler.post { checkCanRecord() } } override fun close() { diff --git a/sentry-android-replay/src/test/java/io/sentry/android/replay/ReplayIntegrationTest.kt b/sentry-android-replay/src/test/java/io/sentry/android/replay/ReplayIntegrationTest.kt index 32b7f4e9285..c356851c213 100644 --- a/sentry-android-replay/src/test/java/io/sentry/android/replay/ReplayIntegrationTest.kt +++ b/sentry-android-replay/src/test/java/io/sentry/android/replay/ReplayIntegrationTest.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.Bitmap.CompressFormat.JPEG import android.graphics.Bitmap.Config.ARGB_8888 +import android.os.Looper import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import io.sentry.Breadcrumb @@ -73,6 +74,7 @@ import org.mockito.kotlin.reset import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import org.robolectric.Shadows.shadowOf import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) @@ -647,6 +649,7 @@ class ReplayIntegrationTest { replay.register(fixture.scopes, fixture.options) replay.start() replay.onScreenshotRecorded(mock()) + shadowOf(Looper.getMainLooper()).idle() verify(recorder).pause() } From 0f1e537f4256e5c12c7d88aa774a51acf4027b60 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 24 Jul 2026 17:38:57 +0200 Subject: [PATCH 2/2] changelog: Add entry for checkCanRecord deadlock fix (#5837) Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfd2e8d0f78..7c3d84b6151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixes +- Fix potential ANR/deadlock in Session Replay when `checkCanRecord` runs on the replay executor thread ([#5837](https://github.com/getsentry/sentry-java/pull/5837)) - Release `MediaMuxer` when the replay video encoder fails to start to avoid a resource leak ([#5607](https://github.com/getsentry/sentry-java/pull/5607)) ### Performance