Skip to content

Commit 5f1e9d5

Browse files
authored
Merge 5cdf0c4 into f982bad
2 parents f982bad + 5cdf0c4 commit 5f1e9d5

5 files changed

Lines changed: 42 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixes
66

77
- Release `MediaMuxer` when a replay segment has no encodable frames to avoid a resource leak ([#5583](https://github.com/getsentry/sentry-java/pull/5583))
8+
- Release `MediaMuxer` when the replay video encoder fails to start to avoid a resource leak ([#5607](https://github.com/getsentry/sentry-java/pull/5607))
89

910
## 8.44.1
1011

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,16 @@ public class ReplayCache(private val options: SentryOptions, private val replayI
162162
bitRate = bitRate,
163163
),
164164
)
165-
.also { it.start() }
165+
.apply {
166+
// the constructor already opened the MediaMuxer, so release it if start() fails,
167+
// otherwise the encoder is never assigned and its resources leak (CloseGuard warning)
168+
try {
169+
start()
170+
} catch (t: Throwable) {
171+
release()
172+
throw t
173+
}
174+
}
166175
}
167176

168177
val step = 1000 / frameRate.toLong()

sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleVideoEncoder.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,10 +289,12 @@ internal class SimpleVideoEncoder(
289289
mediaCodec.stop()
290290
mediaCodec.release()
291291
surface?.release()
292-
293-
frameMuxer.release()
294292
} catch (e: Throwable) {
295293
options.logger.log(DEBUG, "Failed to properly release video encoder", e)
294+
} finally {
295+
// always release the muxer, even if draining/stopping the codec above threw (e.g. when the
296+
// encoder failed to fully start), otherwise its MediaMuxer leaks (CloseGuard warning)
297+
frameMuxer.release()
296298
}
297299
}
298300
}

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicReference
2727
import kotlin.test.BeforeTest
2828
import kotlin.test.Test
2929
import kotlin.test.assertEquals
30+
import kotlin.test.assertFailsWith
3031
import kotlin.test.assertFalse
3132
import kotlin.test.assertNull
3233
import kotlin.test.assertTrue
@@ -35,6 +36,7 @@ import org.junit.rules.TemporaryFolder
3536
import org.junit.runner.RunWith
3637
import org.robolectric.annotation.Config
3738
import org.robolectric.shadows.ShadowBitmapFactory
39+
import org.robolectric.shadows.ShadowCloseGuard
3840

3941
@RunWith(AndroidJUnit4::class)
4042
@Config(sdk = [26], shadows = [ReplayShadowMediaCodec::class])
@@ -55,6 +57,7 @@ class ReplayCacheTest {
5557
@BeforeTest
5658
fun `set up`() {
5759
ReplayShadowMediaCodec.framesToEncode = 5
60+
ReplayShadowMediaCodec.throwOnStart = false
5861
ShadowBitmapFactory.setAllowInvalidImageData(true)
5962
}
6063

@@ -92,6 +95,26 @@ class ReplayCacheTest {
9295
assertNull(video)
9396
}
9497

98+
@Test
99+
fun `releases the muxer when the encoder fails to start`() {
100+
ReplayShadowMediaCodec.throwOnStart = true
101+
val replayCache = fixture.getSut(tmpDir)
102+
103+
val bitmap = Bitmap.createBitmap(1, 1, ARGB_8888)
104+
replayCache.addFrame(bitmap, 1)
105+
106+
ShadowCloseGuard.reset()
107+
assertFailsWith<IllegalStateException> {
108+
replayCache.createVideoOf(5000L, 0, 0, 100, 200, 1, 20_000)
109+
}
110+
111+
val muxerLeaks =
112+
ShadowCloseGuard.getErrors().filter { error ->
113+
error.stackTrace.any { it.className.contains("MediaMuxer") }
114+
}
115+
assertTrue(muxerLeaks.isEmpty(), "MediaMuxer was not released: $muxerLeaks")
116+
}
117+
95118
@Test
96119
fun `deletes frames after creating a video`() {
97120
ReplayShadowMediaCodec.framesToEncode = 3

sentry-android-replay/src/test/java/io/sentry/android/replay/util/ReplayShadowMediaCodec.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@ class ReplayShadowMediaCodec : ShadowMediaCodec() {
1515
companion object {
1616
var frameRate = 1
1717
var framesToEncode = 5
18+
var throwOnStart = false
1819
}
1920

2021
private val encoded = AtomicBoolean(false)
2122

2223
@Implementation
2324
fun start() {
25+
if (throwOnStart) {
26+
throw IllegalStateException("Simulated codec start failure")
27+
}
2428
super.native_start()
2529
}
2630

0 commit comments

Comments
 (0)