Skip to content

Commit ade58cc

Browse files
romtsnclaude
andcommitted
fix(replay): Post checkCanRecord to main thread to prevent deadlock
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) <noreply@anthropic.com>
1 parent 6335e35 commit ade58cc

2 files changed

Lines changed: 9 additions & 2 deletions

File tree

sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,11 @@ public class ReplayIntegration(
352352
}
353353
addFrame(bitmap, frameTimeStamp, screen)
354354
}
355-
checkCanRecord()
355+
// Post to the main thread: onScreenshotRecorded can run on the replay executor
356+
// (PixelCopy masked-capture and emit paths), and checkCanRecord -> pauseInternal
357+
// acquires lifecycleLock — if another thread holds that lock while submitting to
358+
// the same executor, we deadlock.
359+
mainLooperHandler.post { checkCanRecord() }
356360
}
357361

358362
override fun onScreenshotRecorded(screenshot: File, frameTimestamp: Long) {
@@ -375,7 +379,7 @@ public class ReplayIntegration(
375379
}
376380
addFrame(screenshot, frameTimestamp, screen)
377381
}
378-
checkCanRecord()
382+
mainLooperHandler.post { checkCanRecord() }
379383
}
380384

381385
override fun close() {

sentry-android-replay/src/test/java/io/sentry/android/replay/ReplayIntegrationTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.content.Context
44
import android.graphics.Bitmap
55
import android.graphics.Bitmap.CompressFormat.JPEG
66
import android.graphics.Bitmap.Config.ARGB_8888
7+
import android.os.Looper
78
import androidx.test.core.app.ApplicationProvider
89
import androidx.test.ext.junit.runners.AndroidJUnit4
910
import io.sentry.Breadcrumb
@@ -73,6 +74,7 @@ import org.mockito.kotlin.reset
7374
import org.mockito.kotlin.times
7475
import org.mockito.kotlin.verify
7576
import org.mockito.kotlin.whenever
77+
import org.robolectric.Shadows.shadowOf
7678
import org.robolectric.annotation.Config
7779

7880
@RunWith(AndroidJUnit4::class)
@@ -647,6 +649,7 @@ class ReplayIntegrationTest {
647649
replay.register(fixture.scopes, fixture.options)
648650
replay.start()
649651
replay.onScreenshotRecorded(mock<Bitmap>())
652+
shadowOf(Looper.getMainLooper()).idle()
650653

651654
verify(recorder).pause()
652655
}

0 commit comments

Comments
 (0)