Skip to content

Commit 6d86a51

Browse files
committed
Synchronize bitmap modifications
Requires to use synchronized blocks over AutoClosableReentrantLock, as only the bitmap object is shared across Strategy <-> ReplayCache. This ensure bitmap.compress is not called in parallel to bitmap.recycle
1 parent b77456b commit 6d86a51

File tree

3 files changed

+31
-20
lines changed

3 files changed

+31
-20
lines changed

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,16 @@ public class ReplayCache(private val options: SentryOptions, private val replayI
7979
replayCacheDir?.mkdirs()
8080

8181
val screenshot = File(replayCacheDir, "$frameTimestamp.jpg").also { it.createNewFile() }
82-
screenshot.outputStream().use {
83-
bitmap.compress(JPEG, options.sessionReplay.quality.screenshotQuality, it)
84-
it.flush()
82+
synchronized(bitmap) {
83+
if (bitmap.isRecycled) {
84+
return
85+
}
86+
screenshot.outputStream().use {
87+
bitmap.compress(JPEG, options.sessionReplay.quality.screenshotQuality, it)
88+
it.flush()
89+
}
90+
addFrame(screenshot, frameTimestamp, screen)
8591
}
86-
87-
addFrame(screenshot, frameTimestamp, screen)
8892
}
8993

9094
/**

sentry-android-replay/src/main/java/io/sentry/android/replay/screenshot/CanvasStrategy.kt

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ internal class CanvasStrategy(
5252

5353
@Volatile private var screenshot: Bitmap? = null
5454

55+
// Lock to synchronize screenshot creation
5556
private val screenshotLock = AutoClosableReentrantLock()
5657
private val prescaledMatrix by
5758
lazy(NONE) { Matrix().apply { preScale(config.scaleFactorX, config.scaleFactorY) } }
@@ -71,18 +72,23 @@ internal class CanvasStrategy(
7172
if (image.planes.size > 0) {
7273
val plane = image.planes[0]
7374

74-
screenshotLock.acquire().use {
75-
if (screenshot == null) {
76-
screenshot =
77-
Bitmap.createBitmap(holder.width, holder.height, Bitmap.Config.ARGB_8888)
78-
}
79-
val bitmap = screenshot
80-
if (bitmap == null || bitmap.isRecycled) {
81-
return@use
75+
if (screenshot == null) {
76+
screenshotLock.acquire().use {
77+
if (screenshot == null) {
78+
screenshot =
79+
Bitmap.createBitmap(holder.width, holder.height, Bitmap.Config.ARGB_8888)
80+
}
8281
}
82+
}
8383

84+
val bitmap = screenshot
85+
if (bitmap != null) {
8486
val buffer = plane.buffer.rewind()
85-
bitmap.copyPixelsFromBuffer(buffer)
87+
synchronized(bitmap) {
88+
if (!bitmap.isRecycled) {
89+
bitmap.copyPixelsFromBuffer(buffer)
90+
}
91+
}
8692
lastCaptureSuccessful.set(true)
8793
screenshotRecorderCallback?.onScreenshotRecorded(bitmap)
8894
}
@@ -183,14 +189,15 @@ internal class CanvasStrategy(
183189

184190
override fun close() {
185191
isClosed.set(true)
186-
screenshotLock.acquire().use {
187-
screenshot?.apply {
188-
if (!isRecycled) {
189-
recycle()
192+
screenshot?.let {
193+
synchronized(it) {
194+
if (!it.isRecycled) {
195+
it.recycle()
190196
}
191197
}
192-
screenshot = null
193198
}
199+
screenshot = null
200+
194201
// the image can be free, unprocessed or in transit
195202
freePictureRef.getAndSet(null)?.reader?.close()
196203
unprocessedPictureRef.getAndSet(null)?.reader?.close()

sentry-android-replay/src/main/java/io/sentry/android/replay/screenshot/PixelCopyStrategy.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ internal class PixelCopyStrategy(
166166

167167
override fun close() {
168168
if (!screenshot.isRecycled) {
169-
screenshot.recycle()
169+
synchronized(screenshot) { screenshot.recycle() }
170170
}
171171
}
172172

0 commit comments

Comments
 (0)