Post-Mortem: Memory Leak Frame Buffer WebRTC di AHM Remote Control
Bedah bug tengah malam ketika capture layar desktop C# menahan referensi GDI bitmap dan membuat RAM membengkak hingga OOM setelah 40 menit streaming.
Gejala Awal di Lapangan
Saat menguji AHM Remote Control lintas kota melalui koneksi WebRTC + signaling :9098, sesi awalnya berjalan mulus pada 60 FPS. Namun sekitar menit ke-35 hingga ke-45, memori proses AhmDesktopHost.exe menanjak dari 68 MB ke lebih dari 1.8 GB sebelum frame mulai freeze.
Akar Masalah: Bitmap Handle & Array Pool Tidak Didaur Ulang
Di loop capture GDI+, setiap frame baru mengalokasikan Bitmap dan buffer MemoryStream baru tanpa using yang deterministik ketika encoding JPEG berkecepatan tinggi mengalami backpressure di antrean WebSocket.
// Sebelum: alokasi ulang tiap frame menyebabkan LOH fragmentation
byte[] CaptureFrame(Rectangle bounds) {
var bmp = new Bitmap(bounds.Width, bounds.Height);
using var g = Graphics.FromImage(bmp);
g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
var ms = new MemoryStream();
bmp.Save(ms, jpegEncoder, encoderParams);
return ms.ToArray(); // bmp tidak selalu ter-dispose cepat saat exception
}Perbaikan Permanen dengan Reusable Buffer
Kita memindahkan instance Bitmap, Graphics, dan MemoryStream ke reusable frame slot dan menerapkan mekanisme drop-frame ketika klien WebRTC/WebSocket belum selesai mengonsumsi frame sebelumnya.
private readonly MemoryStream _frameStream = new(256 * 1024);
public ReadOnlyMemory<byte> CaptureReusable(Bitmap canvas, Graphics gfx, Rectangle bounds) {
gfx.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
_frameStream.SetLength(0);
canvas.Save(_frameStream, _jpegCodec, _encoderParams);
return _frameStream.GetBuffer().AsMemory(0, (int)_frameStream.Length);
}Hasil Setelah Deploy
- Penggunaan RAM stabil di kisaran 52–64 MB bahkan setelah streaming non-stop 6 jam.
- Latensi rata-rata turun karena GC Gen 2 nyaris tidak lagi terpicu di tengah sesi.