Skip to content

v0.1.3 — the out-of-memory crash, fixed and measured

Choose a tag to compare

@Alihkhawaher Alihkhawaher released this 11 Oct 10:17
· 1 commit to main since this release

v0.1.3 — the out-of-memory crash, fixed and measured

Two crashes on 2026-10-11 killed this app and the Windows desktop process:

time committed memory process lifetime
08:22:57 485,745,373,184 B (452 GiB) 10 h 30 m
12:17:23 478,142,341,120 B (445 GiB) 5 h 55 m

Both numbers come from Windows itself (Resource-Exhaustion-Detector, event 2004).
When commit ran out, dwm.exe — the compositor that draws the desktop — died with
it, and so did WhatsApp.

What happened

Frames were handed to the UI through an unbounded mpsc::channel(), which never
blocks. A minimised or invisible window is never repainted on Windows (eframe
says so itself: "These windows don't receive RedrawRequested events on Windows"),
so the UI stopped draining while capture kept producing decoded 1920×1080 frames —
6,220,800 bytes each. The arithmetic names the contents exactly:

478,142,341,120 / 6,220,800 = 76,861.9 frames
485,745,373,184 / 6,220,800 = 78,084.1 frames

Measured production rate here: ~30 fps, about 186 MB/s — 478 GB in roughly
45 minutes of an unpainted window.

The fix

  • The frame queue is now one frame long, by construction. Frames wait in a
    FrameSlot; a newer frame replaces the waiting one. A queue with no bound is only
    safe while its consumer keeps consuming, and a UI does not. This is also the more
    correct
    behaviour for live video: the newest frame, never a backlog.
  • Backpressure. If nothing has been collected for two seconds, capture keeps
    reading samples (so the picture is current the instant the window returns) but
    stops converting them. A stalled UI now costs one frame and no CPU.
  • A ceiling on committed memory (src/proc_mem.rs, GetProcessMemoryInfo):
    checked once a second, 4 GiB by default, KVM_MAX_COMMIT_MB to override. Past it,
    capture stops and says so. A bug should be able to damage this app, never the
    machine it runs on.
  • A durable log for GUI runs: %APPDATA%\KVM-Control Plus\last-run.log, with
    stderr aimed at the same file — the message that would have named the failing
    allocation went to a console the GUI had given back to Windows, so it was lost.

Verifying it yourself

--soak <seconds> reproduces the exact condition that was fatal — a producer with
nobody collecting — and prints the committed memory while it happens:

$ KVM-Control-Plus.exe --soak 20
produced 61, collected 0, peak commit 18.8 MB, ceiling 4096 MB
PASS — 61 frames produced with nobody collecting, memory stayed flat

Production stops climbing partway through: that is the backpressure working, and it
is the column that used to climb forever, 6 MB at a time.

The ceiling can be tested without risking anything:

$ KVM_MAX_COMMIT_MB=1 KVM-Control-Plus.exe -v --soak 8
[ERROR] committed 10 MB, past this app's 1 MB ceiling — capture stopped to protect the machine

Plus 67 unit tests (was 62), covering the slot holding one frame however many
arrive uncollected, the "nobody is collecting" signal, and a malformed
KVM_MAX_COMMIT_MB being unable to remove the ceiling.

Full post-mortem, with the event-log evidence and the frame arithmetic:
docs/CRASH_2026-10-11.md.

Everything from v0.1.2 is still here

Correct colour range and matrix read off the negotiated media type (the picture was
too bright), low-latency capture, and measured pipeline latency.

Still two dependencies. This fix adds no crates — GetProcessMemoryInfo comes
from the windows crate already in use.

Asset: KVM-Control-Plus.exe — one self-contained file, no runtime to install.