fix(kvm): treat an absent display as retryable in capture loops #121

Merged
Claude-Run merged 1 commit from fix/VA-119-kvm-capture-retryable-absent-display into main 2026-07-06 13:09:55 +02:00
Member

Both long-lived KVM capture loops (the tile stream in dispatch.rs and the native VNC main_loop in vnc_native/server.rs) terminated on the first capture error, including the transient "display not ready" case (KvmError::NotAvailable) and the equally-transient CaptureFailed. A momentarily-absent display is normal and self-healing (early boot, a VT switch, a display-manager restart, a GPU modeset, a session teardown), so the loops died on conditions a hosting-company console rides through with a spinner.

Both loops now treat NotAvailable and CaptureFailed as retryable. On a capture error the loop enters a slow-probe state on a fixed 1000ms cadence decoupled from FPS, so it does not hammer the backend during a long outage, and resumes automatically on the next successful capture. The only remaining terminators are the cancel signal and a closed outbox (and, for VNC, a client disconnect).

Tile stream: capture is split from the diff so StreamState::invalidate runs on recovery and the first frame after a gap re-ships the whole screen. The fatal kvm_stream_error return path is removed; a new kvm_stream_status action carrying state ("waiting"/"streaming") and the last error text is emitted only on a waiting<->streaming transition so the operator UI can show "waiting for display". run_kvm_stream is now generic over the shared CaptureSource so the state machine is testable with a mock.

Native VNC: main_loop captures before diffing and no longer returns Err on a capture failure; it holds the connection, keeps pending_update, backs the frame timer off to the probe cadence, and clears session.prev on entering waiting so recovery sends a full-framebuffer update rather than leaving the client on a stale partial frame. RFB is incremental, so the client keeps its last frame on screen through the gap.

Transition logging is throttled to one line per state change (entering waiting, resuming), never per probe tick.

Tests: a mock-CaptureSource tile-stream test drives NotAvailable -> CaptureFailed -> Ok and asserts the loop stays alive, emits exactly one waiting and one streaming transition, re-ships a full frame on resume, and never emits kvm_stream_error. A VNC main_loop test feeds the same failure-then-recovery sequence and asserts the connection stays open and a full-framebuffer update arrives on recovery.

#VA-119

Both long-lived KVM capture loops (the tile stream in `dispatch.rs` and the native VNC `main_loop` in `vnc_native/server.rs`) terminated on the first capture error, including the transient "display not ready" case (`KvmError::NotAvailable`) and the equally-transient `CaptureFailed`. A momentarily-absent display is normal and self-healing (early boot, a VT switch, a display-manager restart, a GPU modeset, a session teardown), so the loops died on conditions a hosting-company console rides through with a spinner. Both loops now treat `NotAvailable` and `CaptureFailed` as retryable. On a capture error the loop enters a slow-probe state on a fixed 1000ms cadence decoupled from FPS, so it does not hammer the backend during a long outage, and resumes automatically on the next successful capture. The only remaining terminators are the cancel signal and a closed outbox (and, for VNC, a client disconnect). Tile stream: capture is split from the diff so `StreamState::invalidate` runs on recovery and the first frame after a gap re-ships the whole screen. The fatal `kvm_stream_error` return path is removed; a new `kvm_stream_status` action carrying `state` ("waiting"/"streaming") and the last error text is emitted only on a waiting<->streaming transition so the operator UI can show "waiting for display". `run_kvm_stream` is now generic over the shared `CaptureSource` so the state machine is testable with a mock. Native VNC: `main_loop` captures before diffing and no longer returns `Err` on a capture failure; it holds the connection, keeps `pending_update`, backs the frame timer off to the probe cadence, and clears `session.prev` on entering waiting so recovery sends a full-framebuffer update rather than leaving the client on a stale partial frame. RFB is incremental, so the client keeps its last frame on screen through the gap. Transition logging is throttled to one line per state change (entering waiting, resuming), never per probe tick. Tests: a mock-`CaptureSource` tile-stream test drives NotAvailable -> CaptureFailed -> Ok and asserts the loop stays alive, emits exactly one waiting and one streaming transition, re-ships a full frame on resume, and never emits `kvm_stream_error`. A VNC `main_loop` test feeds the same failure-then-recovery sequence and asserts the connection stays open and a full-framebuffer update arrives on recovery. #VA-119
fix(kvm): treat an absent display as retryable in capture loops
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 4m1s
Create release / Create release from merged PR (pull_request) Has been skipped
0497cfab0f
Both long-lived KVM capture loops (the tile stream in `dispatch.rs` and the native VNC `main_loop` in `vnc_native/server.rs`) terminated on the first capture error, including the transient "display not ready" case (`KvmError::NotAvailable`) and the equally-transient `CaptureFailed`. A momentarily-absent display is normal and self-healing (early boot, a VT switch, a display-manager restart, a GPU modeset, a session teardown), so the loops died on conditions a hosting-company console rides through with a spinner.

Both loops now treat `NotAvailable` and `CaptureFailed` as retryable. On a capture error the loop enters a slow-probe state on a fixed 1000ms cadence decoupled from FPS, so it does not hammer the backend during a long outage, and resumes automatically on the next successful capture. The only remaining terminators are the cancel signal and a closed outbox (and, for VNC, a client disconnect).

Tile stream: capture is split from the diff so `StreamState::invalidate` runs on recovery and the first frame after a gap re-ships the whole screen. The fatal `kvm_stream_error` return path is removed; a new `kvm_stream_status` action carrying `state` ("waiting"/"streaming") and the last error text is emitted only on a waiting<->streaming transition so the operator UI can show "waiting for display". `run_kvm_stream` is now generic over the shared `CaptureSource` so the state machine is testable with a mock.

Native VNC: `main_loop` captures before diffing and no longer returns `Err` on a capture failure; it holds the connection, keeps `pending_update`, backs the frame timer off to the probe cadence, and clears `session.prev` on entering waiting so recovery sends a full-framebuffer update rather than leaving the client on a stale partial frame. RFB is incremental, so the client keeps its last frame on screen through the gap.

Transition logging is throttled to one line per state change (entering waiting, resuming), never per probe tick.

Tests: a mock-`CaptureSource` tile-stream test drives NotAvailable -> CaptureFailed -> Ok and asserts the loop stays alive, emits exactly one waiting and one streaming transition, re-ships a full frame on resume, and never emits `kvm_stream_error`. A VNC `main_loop` test feeds the same failure-then-recovery sequence and asserts the connection stays open and a full-framebuffer update arrives on recovery.

#VA-119
Claude-Run deleted branch fix/VA-119-kvm-capture-retryable-absent-display 2026-07-06 13:09:55 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-agent!121
No description provided.