Steps to reproduce
Display a continuously rendering external texture (e.g. a WebRTC video stream via flutter_webrtc ≥ 0.12.12, which uses the new TextureRegistry.SurfaceProducer API) in fullscreen on an Android 13+ device, and leave it running.
Expected results
File-descriptor count stays stable; the stream keeps rendering indefinitely.
Actual results
The process leaks one anon:sync_file file descriptor per rendered frame and hits the 32768 RLIMIT_NOFILE ceiling in ~12 min (~2700 fd/min on the test device). After that all new fd operations fail ("Too many open files"), rendering freezes and the app is effectively dead.
Root cause: in the engine, ImageReaderSurfaceProducer.waitOnFence(Image) (and the twin in ImageTextureRegistryEntry) calls image.getFence() and awaitForever() but never fence.close(). SyncFence is AutoCloseable and owns a sync_file fd that the receiver must close. Across the Android embedding there are 2x
getFence() and 0x fence.close(). The fence-wait path is gated on SDK_INT >= 33, so it affects Android 13+. Present in stable 3.41.7 and current master.
Suggested fix — close the fence after awaiting (try-with-resources):
try (SyncFence fence = image.getFence()) {
fence.awaitForever();
} catch (IOException e) { /* drop */ }
Code sample
Code sample
// Flutter engine source: FlutterRenderer.java -> ImageReaderSurfaceProducer.waitOnFence
// (stable 3.41.7 and master)
@RequiresApi(API_LEVELS.API_33)
private void waitOnFence(Image image) {
try {
SyncFence fence = image.getFence();
fence.awaitForever();
} catch (IOException e) {
// Drop.
}
}
// fence (a SyncFence wrapping a sync_file FD) is never close()d.
Screenshots or Video
Screenshots / Video demonstration
[Upload media here]
Logs
Logs
Samsung SM-P619, Android 14. Per-minute fd breakdown from /proc/self/fd:
anon:sync_file 2842 -> 32129 over ~12 min, while socket/file/etc. stay flat.
Then: fcntl(F_DUPFD_CLOEXEC) failed ... Too many open files.
Forcing the legacy SurfaceTexture path (debugForceSurfaceProducerGlTextures=true) did NOT reliably avoid it.
Related: #143777 (closed not planned), #84702, flutter-webrtc/flutter-webrtc#1974.
Flutter Doctor output
Doctor output
[✓] Flutter (Channel stable, 3.41.7, on Linux Mint 22.3 6.17.0-23-generic, locale de_DE.UTF-8)
• Flutter version 3.41.7 on channel stable at /home/macyo/development/flutter
• Framework revision cc0734ac71 (2026-04-15), Engine revision 59aa584fdf
• Dart version 3.11.5, DevTools version 2.54.2
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
• Platform android-36, build-tools 35.0.0
• Java version OpenJDK Runtime Environment (build 17.0.18)
• All Android licenses accepted.
[✓] Chrome - develop for the web
[✓] Linux toolchain - develop for Linux desktop
Steps to reproduce
Display a continuously rendering external texture (e.g. a WebRTC video stream via
flutter_webrtc≥ 0.12.12, which uses the newTextureRegistry.SurfaceProducerAPI) in fullscreen on an Android 13+ device, and leave it running.Expected results
File-descriptor count stays stable; the stream keeps rendering indefinitely.
Actual results
The process leaks one anon:sync_file file descriptor per rendered frame and hits the 32768 RLIMIT_NOFILE ceiling in ~12 min (~2700 fd/min on the test device). After that all new fd operations fail ("Too many open files"), rendering freezes and the app is effectively dead.
Root cause: in the engine, ImageReaderSurfaceProducer.waitOnFence(Image) (and the twin in ImageTextureRegistryEntry) calls image.getFence() and awaitForever() but never fence.close(). SyncFence is AutoCloseable and owns a sync_file fd that the receiver must close. Across the Android embedding there are 2x
getFence() and 0x fence.close(). The fence-wait path is gated on SDK_INT >= 33, so it affects Android 13+. Present in stable 3.41.7 and current master.
Suggested fix — close the fence after awaiting (try-with-resources):
try (SyncFence fence = image.getFence()) {
fence.awaitForever();
} catch (IOException e) { /* drop */ }
Code sample
Code sample
// Flutter engine source: FlutterRenderer.java -> ImageReaderSurfaceProducer.waitOnFence // (stable 3.41.7 and master) @RequiresApi(API_LEVELS.API_33) private void waitOnFence(Image image) { try { SyncFence fence = image.getFence(); fence.awaitForever(); } catch (IOException e) { // Drop. } } // fence (a SyncFence wrapping a sync_file FD) is never close()d.Screenshots or Video
Screenshots / Video demonstration
[Upload media here]
Logs
Logs
Flutter Doctor output
Doctor output