From b47d015e0eaecbeb073d0939494c410b95d8ffb5 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Mon, 22 Jun 2026 12:07:57 +0200 Subject: [PATCH 1/2] fix(replay): Release MediaMuxer when no frames are encoded The MediaMuxer is created when the video encoder is constructed, but its release() was reachable only on the happy path. Two cases leaked it: - createVideoOf returned early when frameCount was 0 without releasing the encoder. - SimpleMp4FrameMuxer.release() called muxer.stop() before muxer.release(). stop() throws if the muxer was never started (no frame ever muxed), so release() was skipped. This surfaced as a CloseGuard "resource was acquired but never released" warning. Guard stop() behind the started flag so release() is always reached, and release the encoder on the no-frames return path. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/main/java/io/sentry/android/replay/ReplayCache.kt | 4 ++++ .../io/sentry/android/replay/video/SimpleMp4FrameMuxer.kt | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt index 32e42dafac1..b3b9edae055 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt @@ -199,6 +199,10 @@ public class ReplayCache(private val options: SentryOptions, private val replayI if (frameCount == 0) { options.logger.log(DEBUG, "Generated a video with no frames, not capturing a replay segment") + encoderLock.acquire().use { + encoder?.release() + encoder = null + } deleteFile(videoFile) return null } diff --git a/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleMp4FrameMuxer.kt b/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleMp4FrameMuxer.kt index 36741686701..e32af9bb44b 100644 --- a/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleMp4FrameMuxer.kt +++ b/sentry-android-replay/src/main/java/io/sentry/android/replay/video/SimpleMp4FrameMuxer.kt @@ -67,7 +67,11 @@ internal class SimpleMp4FrameMuxer(path: String, fps: Float) : SimpleFrameMuxer } override fun release() { - muxer.stop() + // stop() throws if the muxer was never started (e.g. no frame was ever muxed), so we guard it + // to ensure release() is always reached and the underlying resources are freed + if (started) { + muxer.stop() + } muxer.release() } From 63aa2407727f0bd2292e50d18f89161b9f8d8523 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Mon, 22 Jun 2026 12:08:39 +0200 Subject: [PATCH 2/2] changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3bdcd38bc4..42312126614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- 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.44.1 ### Fixes