From 3a0bdc86453ad87d56d251e6600aa62372846b59 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 22 Sep 2026 06:21:05 +0000 Subject: [PATCH] export: stop free_buffers leak on single-plane cursor/items streams The writer recycled every finished frame buffer into free_buffers, but buffers are only popped back out in the dual-plane producer branch. On single-plane exports (cursor-only or items-only) the pool therefore grew by one plate per frame (~7-8 MiB) until the process ran out of memory: ~14 GB over a 60 s 30 fps export. Gate recycling on dual_plane. Co-Authored-By: Jean de Dieu HAGENIMANA --- apps/desktop/src-tauri/src/exports/mod.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src-tauri/src/exports/mod.rs b/apps/desktop/src-tauri/src/exports/mod.rs index 20828dc7..bffe8e89 100644 --- a/apps/desktop/src-tauri/src/exports/mod.rs +++ b/apps/desktop/src-tauri/src/exports/mod.rs @@ -2409,8 +2409,11 @@ fn feed_cursor_frames( let abort = std::sync::atomic::AtomicBool::new(false); let (frame_tx, frame_rx) = std::sync::mpsc::channel::<(u64, ProducedFrame)>(); // Finished buffers are recycled back to workers to avoid an 8-33 MiB - // allocation + zeroing on every frame. + // allocation + zeroing on every frame. Only the dual-plane branch below + // pops them — on a single-plane stream pushing every finished frame + // would grow the pool by one plate per frame until memory is exhausted. let free_buffers = Mutex::new(Vec::>::new()); + let dual_plane = cursor.dual_plane; let result = std::thread::scope(|scope| -> Result<()> { for _ in 0..workers { @@ -2428,7 +2431,6 @@ fn feed_cursor_frames( let height = cursor.canvas_height; let cursor_rect = cursor.cursor_rect; let fps = cursor.fps; - let dual_plane = cursor.dual_plane; scope.spawn(move || { let run = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let mut state = match OverlayWorkerState::new( @@ -2570,10 +2572,12 @@ fn feed_cursor_frames( ); } if let ProducedFrame::Bytes(bytes) = produced { - free_buffers - .lock() - .unwrap_or_else(|poisoned| poisoned.into_inner()) - .push(bytes); + if dual_plane { + free_buffers + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .push(bytes); + } } expected += 1; let mut guard = dispatch