Skip to content

Sentry Session Replay File Descriptor Leak #3528

Description

@vilorel

Platform

Flutter Mobile iOS

Obfuscation

Enabled

Debug Info

Enabled

Doctor

flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.41.1, on macOS 26.2 25C56 darwin-arm64, locale en-US)
[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0-rc1)
[✓] Xcode - develop for iOS and macOS (Xcode 26.2)
[✓] Chrome - develop for the web
[✓] Connected device (4 available)
[✓] Network resources

• No issues found!

Version

9.13.0

Steps to Reproduce

The app froze due to the process exhausting its file descriptor limit (EMFILE / errno-24), triggered by Sentry's session replay feature running continuously while being rate-limited by the Sentry server.

Timeline (app started at 13:07:21)

  1. 13:07 – Normal startup. The AVC video encoder (c2.android.avc.encoder) starts cycling for Sentry session replay — allocating codecs, buffer pools, MPEG4
    writers, encoding a few frames, then resetting. This cycle repeats at least 6 times visible in the log.
  2. Sentry 429 rate limiting begins. Sentry's server rejects replay envelopes with HTTP 429 (lines 1118, 1516, 1854):
    "Sentry dropped data due to a quota or internal rate limit being reached."
  3. Despite being rate-limited, the replay keeps recording — each cycle allocates new codec instances, buffer pools, and file handles that are not being reclaimed
    properly.
  4. ~13:18 – File descriptor limit reached. The cascade of failures begins:
- Line 2031 — Vulkan swapchain dies: Could not acquire next swapchain image: ErrorOutOfHostMemory
- Line 2032 — GPU surface gone: No surface available
- Line 2033 — Impeller fence waiter tears down: Fence waiter encountered an unexpected error. Tearing down the waiter thread.

This is a fatal, unrecoverable GPU rendering failure — Flutter/Impeller can no longer draw any frames.
4. Post-crash fallout (all caused by Too many open files):
[...]

Expected Result

The library should close codecs/buffers when rate-limited and limit is hit or not (re)attempt the recording in the first place.

Actual Result

D/Sentry (32718): Request failed, API returned 429
D/Sentry (32718): {"detail":"Sentry dropped data due to a quota or internal rate limit being reached. This will not affect your application. See https://docs.sentry.io/product/accounts/quotas/ for more information."}
D/Sentry (32718): The transport failed to send the envelope with response code 429
D/CCodec (32718): allocate(c2.android.avc.encoder)
E/Sentry (32718): Envelope submission failed
E/Sentry (32718): java.lang.IllegalStateException: The transport failed to send the envelope with response code 429
E/Sentry (32718): at io.sentry.transport.AsyncHttpTransport$EnvelopeSender.flush(AsyncHttpTransport.java:322)
E/Sentry (32718): at io.sentry.transport.AsyncHttpTransport$EnvelopeSender.run(AsyncHttpTransport.java:247)
E/Sentry (32718): at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:520)
E/Sentry (32718): at java.util.concurrent.FutureTask.run(FutureTask.java:317)
E/Sentry (32718): at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1154)
E/Sentry (32718): at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:652)
E/Sentry (32718): at java.lang.Thread.run(Thread.java:1564)
I/CCodec (32718): setting up 'default' as default (vendor) store
W/xxxx(32718): AIBinder_linkToDeath is being called with a non-null cookie and no onUnlink callback set. This might not be intended. AIBinder_DeathRecipient_setOnUnlinked should be called first.
I/CCodec (32718): Created component [c2.android.avc.encoder] for [c2.android.avc.encoder]
D/CCodecConfig(32718): read media type: video/avc
D/ReflectedParamUpdater(32718): extent() != 1 for single value type: algo.buffers.max-count.values
D/ReflectedParamUpdater(32718): extent() != 1 for single value type: output.subscribed-indices.values
D/ReflectedParamUpdater(32718): extent() != 1 for single value type: input.buffers.allocator-ids.values
D/ReflectedParamUpdater(32718): extent() != 1 for single value type: output.buffers.allocator-ids.values
D/ReflectedParamUpdater(32718): extent() != 1 for single value type: algo.buffers.allocator-ids.values
D/ReflectedParamUpdater(32718): extent() != 1 for single value type: output.buffers.pool-ids.values
D/ReflectedParamUpdater(32718): extent() != 1 for single value type: algo.buffers.pool-ids.values
D/ReflectedParamUpdater(32718): ignored struct field coding.gop.values
D/ReflectedParamUpdater(32718): ignored struct field coding.qp.values
D/CCodecConfig(32718): ignoring local param raw.color (0xc2001809) as it is already supported
I/CCodecConfig(32718): query failed after returning 13 values (BAD_INDEX)
D/CCodecConfig(32718): c2 config diff is Dict {
D/CCodecConfig(32718): c2::u32 algo.bitrate-mode.value = 3
D/CCodecConfig(32718): c2::i32 coded.average-qp.value = 0
D/CCodecConfig(32718): c2::u32 coded.bitrate.value = 64000
D/CCodecConfig(32718): c2::float coded.frame-rate.value = 1
D/CCodecConfig(32718): c2::u32 coded.picture-type.value = 0
D/CCodecConfig(32718): c2::u32 coded.pl.level = 20492
D/CCodecConfig(32718): c2::u32 coded.pl.profile = 20481
D/CCodecConfig(32718): c2::u32 coded.vui.color.matrix = 0
D/CCodecConfig(32718): c2::u32 coded.vui.color.primaries = 0
D/CCodecConfig(32718): c2::u32 coded.vui.color.range = 0
D/CCodecConfig(32718): c2::u32 coded.vui.color.transfer = 0
D/CCodecConfig(32718): c2::u32 coding.intra-refresh.mode = 0
D/CCodecConfig(32718): c2::float coding.intra-refresh.period = 0
D/CCodecConfig(32718): c2::u32 coding.request-sync-frame.value = 0
D/CCodecConfig(32718): c2::i64 coding.sync-frame-interval.value = 1000000
D/CCodecConfig(32718): c2::u32 input.delay.value = 0
D/CCodecConfig(32718): string input.media-type.value = "video/raw"
D/CCodecConfig(32718): string output.media-type.value = "video/avc"
D/CCodecConfig(32718): c2::u32 raw.color.matrix = 0
D/CCodecConfig(32718): c2::u32 raw.color.primaries = 0
D/CCodecConfig(32718): c2::u32 raw.color.range = 0
D/CCodecConfig(32718): c2::u32 raw.color.transfer = 0
D/CCodecConfig(32718): c2::u32 raw.size.height = 16
D/CCodecConfig(32718): c2::u32 raw.size.width = 16
D/CCodecConfig(32718): }
W/ColorUtils(32718): expected specified color aspects (0:0:0:0)
D/MPEG4Writer(32718): PreAllocation enabled
I/MediaCodec(32718): media_quality service unavailable, skipping updatePictureProfile
D/VQApply (32718): raise bitrate: configured 1 to floor 1158144
D/VQApply (32718): minquality by QP: inject video-qp-max=38
D/VQApply (32718): minquality/target bitrate raised from 1 to 1158144 bps
D/MediaCodec(32718): shapeMediaFormat: deltas(6): AMessage(what = 0x00000000) = {
D/MediaCodec(32718): int32_t bitrate = 1158144
D/MediaCodec(32718): int32_t android._encoding-quality-level = 0
D/MediaCodec(32718): int32_t video-qp-max = 38
D/MediaCodec(32718): int32_t video-qp-i-max = 38
D/MediaCodec(32718): int32_t video-qp-p-max = 41
D/MediaCodec(32718): int32_t video-qp-b-max = 44
D/MediaCodec(32718): }
D/CCodec (32718): [c2.android.avc.encoder] buffers are bound to CCodec for this session
D/CCodecConfig(32718): no c2 equivalents for color-format
D/CCodecConfig(32718): no c2 equivalents for video-qp-max
D/CCodecConfig(32718): no c2 equivalents for video-qp-i-max
D/CCodecConfig(32718): no c2 equivalents for video-qp-p-max
D/CCodecConfig(32718): no c2 equivalents for video-qp-b-max
D/CCodecConfig(32718): no c2 equivalents for flags
D/CCodecConfig(32718): no c2 equivalents for encoder
D/CCodecConfig(32718): c2 config diff is c2::u32 coded.bitrate.value = 1158144
D/CCodecConfig(32718): c2::i64 coding.sync-frame-interval.value = 6000000
D/CCodecConfig(32718): c2::u32 raw.size.height = 928
D/CCodecConfig(32718): c2::u32 raw.size.width = 416
W/ColorUtils(32718): expected specified color aspects (0:0:0:0)
D/CCodec (32718): encoding statistics level = 0
D/CCodec (32718): setup formats input: AMessage(what = 0x00000000) = {
D/CCodec (32718): Rect crop(0, 0, 415, 927)
D/CCodec (32718): int32_t color-standard = 0
D/CCodec (32718): int32_t color-range = 0
D/CCodec (32718): int32_t color-transfer = 0
D/CCodec (32718): int32_t frame-rate = 1
D/CCodec (32718): int32_t height = 928
D/CCodec (32718): int32_t intra-refresh-period = 0
D/CCodec (32718): string mime = "video/raw"
D/CCodec (32718): int32_t video-qp-average = 0
D/CCodec (32718): int32_t width = 416
D/CCodec (32718): int32_t android._dataspace = 0
D/CCodec (32718): int32_t using-sw-read-often = 1
D/CCodec (32718): int64_t android._C2MemoryUsage = 1
D/CCodec (32718): int32_t color-format = 2130708361
D/CCodec (32718): int32_t android._color-format = 2130708361
D/CCodec (32718): }
D/CCodec (32718): setup formats output: AMessage(what = 0x00000000) = {
D/CCodec (32718): int32_t bitrate = 1158144
D/CCodec (32718): int32_t bitrate-mode = 1
D/CCodec (32718): Rect crop(0, 0, 415, 927)
D/CCodec (32718): int32_t color-standard = 0
D/CCodec (32718): int32_t color-range = 0
D/CCodec (32718): int32_t color-transfer = 0
D/CCodec (32718): int32_t frame-rate = 1
D/CCodec (32718): int32_t height = 928
D/CCodec (32718): int32_t intra-refresh-period = 0
D/CCodec (32718): int32_t level = 4096
D/CCodec (32718): int32_t max-bitrate = 1158144
D/CCodec (32718): string mime = "video/avc"
D/CCodec (32718): int32_t profile = 65536
D/CCodec (32718): int32_t video-qp-average = 0
D/CCodec (32718): int32_t width = 416
D/CCodec (32718): }
I/CCodecConfig(32718): query failed after returning 14 values (BAD_INDEX)
E/xxxxx(32718): Failed to query component interface for required system resources: 6
D/CCodec (32718): aidl based PersistentSurface created
D/CCodec (32718): input format changed to AMessage(what = 0x00000000) = {
D/CCodec (32718): Rect crop(0, 0, 415, 927)
D/CCodec (32718): int32_t color-standard = 1
D/CCodec (32718): int32_t color-range = 2
D/CCodec (32718): int32_t color-transfer = 3
D/CCodec (32718): int32_t frame-rate = 1
D/CCodec (32718): int32_t height = 928
D/CCodec (32718): int32_t intra-refresh-period = 0
D/CCodec (32718): string mime = "video/raw"
D/CCodec (32718): int32_t video-qp-average = 0
D/CCodec (32718): int32_t width = 416
D/CCodec (32718): int32_t android._dataspace = 260
D/CCodec (32718): int32_t using-sw-read-often = 1
D/CCodec (32718): int64_t android._C2MemoryUsage = 1
D/CCodec (32718): int32_t color-format = 2130708361
D/CCodec (32718): int32_t android._color-format = 2130708361
D/CCodec (32718): }
D/GraphicBufferSource(32718): setting dataspace: 0x10c10000, acquired=0
D/CCodec (32718): ISConfig not changed
D/CCodecBufferChannel(32718): [c2.android.avc.encoder#434] Created input block pool with allocatorID 17 => poolID 18 - OK (0)
I/CCodecBufferChannel(32718): [c2.android.avc.encoder#434] Created output block pool with allocatorID 16 => poolID 174 - OK
D/CCodecBufferChannel(32718): [c2.android.avc.encoder#434] Configured output block pool ids 174 => OK
D/GraphicBufferSource(32718): got buffer with new dataSpace 0x8810000
D/C2NodeImpl(32718): dataspace changed to 0x8810000 pixel format: 0x1
D/CCodecConfig(32718): c2 config diff is Buffer coded.init-data.value = {
D/CCodecConfig(32718): 00000000: 00 00 00 01 67 42 c0 29 8d 68 1a 07 5a 46 02 1a ....gB.).h..ZF..
D/CCodecConfig(32718): 00000010: 02 0f 08 84 6a 00 00 00 01 68 ce 01 a8 35 c8 ....j....h...5.
D/CCodecConfig(32718): }
D/MPEG4Writer(32718): fpathconf _PC_FILESIZEBITS:64
D/MPEG4Writer(32718): File size limit set to 4503599627370495 bytes implicitly
D/MPEG4Writer(32718): MP4WtrCtrlHlpLooper Started
I/MPEG4Writer(32718): limits: 4503599627370495/0 bytes/us, bit rate: -1 bps and the estimated moov size 3192 bytes
I/MPEG4Writer(32718): setStartTimestampUs: 0
I/MPEG4Writer(32718): Earliest track starting time: 0
D/MPEG4Writer(32718): reset()
D/MPEG4Writer(32718): Video track stopping. Stop source
D/MPEG4Writer(32718): Video track source stopping
D/MPEG4Writer(32718): Video track source stopped
V/MediaWriter(32718): Track event err/info msg:101, trackId:1, type:1000,val:-1011
I/MPEG4Writer(32718): Received total/0-length (2/0) buffers and encoded 2 frames. - Video
D/MPEG4Writer(32718): Video track stopped. Status:0. Stop source
D/MPEG4Writer(32718): 0 chunks are written in the last batch
D/MPEG4Writer(32718): WriterThread stopped. Status:0
I/MPEG4Writer(32718): Adjust the moov start time from 0 us -> 0 us
I/MPEG4Writer(32718): MOOV atom was written to the file
D/MPEG4Writer(32718): release()
D/MPEG4Writer(32718): ftruncate mPreAllocateFileEndOffset:26735 mOffset:809 mMdatEndOffset:25291 diff:1444
D/AidlBufferPool(32718): Destruction - bufferpool2 0xb400007311c6c618 cached: 0/0M, 0/0% in use; allocs: 0, 0% recycled; transfers: 0, 0% unfetched
D/MPEG4Writer(32718): final fsync() takes 5 ms, file size 25291
D/MPEG4Writer(32718): MP4WtrCtrlHlpLooper stopped
D/MPEG4Writer(32718): Top 5 write durations(microseconds): #1:27 #2:44 #3:50 #4:67 #5:76
D/MPEG4Writer(32718): reset()
D/MPEG4Writer(32718): Video track stopping. Stop source
E/MPEG4Writer(32718): Stop() called but track is not started or stopped
W/Sentry (32718): 1 envelope items will be dropped due rate limiting.
W/Sentry (32718): Envelope discarded due all items rate limited.
D/AidlBufferPoolAcc(32718): evictor expired: 1, evicted: 0
D/AidlBufferPoolAcc(32718): evictor expired: 1, evicted: 0
[...]

Are you willing to submit a PR?

None

Metadata

Metadata

Assignees

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions