Repository navigation
v0.1.3 — the out-of-memory crash, fixed and measured
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_MBto 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.