From 2f7c021cac8ca634bfa6f01a2d68f37f53a52e1f Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Tue, 16 Jun 2026 16:20:26 +0000 Subject: [PATCH] fix(host/windows): per-session SudoVDA monitor GUID (stop overlapping-session monitor teardown) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User observed: 'display disconnected' + freeze with NO context change, and 'first switch happy, subsequent slower, then chaos under stress'. Log shows the cause: MONITOR_GUID was a FIXED constant, so overlapping sessions (a client RECONNECTING after a freeze before the old session tore down, or concurrent sessions) all map to the SAME SudoVDA monitor (same GUID -> IOCTL_ADD reuses target 257). When the old session ends, its IOCTL_REMOVE tears the monitor down OUT FROM UNDER the live session -> 'display disconnected' + the late E_INVALIDARG/MODE_CHANGE failures (output vanished mid-session) -> cascade. Fix: next_monitor_guid() returns a unique GUID per (process, session) [base GUID with low 48-bit node = pid<<16 | session#]; create() threads it into AddParams AND the keepalive (which REMOVEs by it). Each session now owns its own monitor; one ending can't kill another. (The 200ms DuplicateOutput1 retry confirmed working — 'succeeded on retry' logged; the residual failures were this collision, not the race.) Co-Authored-By: Claude Opus 4.8 --- crates/punktfunk-host/src/vdisplay/sudovda.rs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/crates/punktfunk-host/src/vdisplay/sudovda.rs b/crates/punktfunk-host/src/vdisplay/sudovda.rs index 66718e2..efc6995 100644 --- a/crates/punktfunk-host/src/vdisplay/sudovda.rs +++ b/crates/punktfunk-host/src/vdisplay/sudovda.rs @@ -60,7 +60,19 @@ const IOCTL_GET_VERSION: u32 = ctl(0x8FF); // A fixed monitor identity. One session at a time today; Windows persists this monitor's layout // across sessions by GUID, and REMOVE keys off it. (TODO: derive per-client when concurrent // sessions land.) -const MONITOR_GUID: GUID = GUID::from_u128(0x70756E6B_7466_756E_6B30_000000000001); +/// A UNIQUE-per-session SudoVDA monitor GUID. The monitor is keyed by GUID for IOCTL_ADD/REMOVE, so a +/// FIXED GUID makes overlapping sessions (a client reconnecting after a freeze before the old session +/// has torn down, or genuine concurrent sessions) all map to the SAME monitor — then one session's +/// IOCTL_REMOVE on teardown tears the monitor down OUT FROM UNDER a still-live session ("display +/// disconnected" sound + freeze, even with no context change — observed live). Make it unique per +/// (process, session): base GUID with the low 48-bit node = (pid << 16 | session#). +fn next_monitor_guid() -> GUID { + use std::sync::atomic::AtomicU32; + static N: AtomicU32 = AtomicU32::new(0); + let n = N.fetch_add(1, Ordering::Relaxed) as u128; + let pid = std::process::id() as u128; + GUID::from_u128(0x70756E6B_7466_756E_6B30_000000000000u128 | (pid << 16) | (n & 0xFFFF)) +} #[repr(C)] #[derive(Clone, Copy)] @@ -664,11 +676,14 @@ impl VirtualDisplay for SudoVdaDisplay { let mut device_name = [0u8; 14]; let nm = b"punktfunk"; device_name[..nm.len()].copy_from_slice(nm); + // Unique GUID PER SESSION so overlapping sessions / client reconnects each own their own + // SudoVDA monitor — a stale session's REMOVE must never tear down a live session's monitor. + let session_guid = next_monitor_guid(); let add = AddParams { width: mode.width, height: mode.height, refresh: mode.refresh_hz, - guid: MONITOR_GUID, + guid: session_guid, device_name, serial: [0u8; 14], }; @@ -802,7 +817,7 @@ impl VirtualDisplay for SudoVdaDisplay { }), keepalive: Box::new(SudoVdaKeepalive { device: device_raw, - guid: MONITOR_GUID, + guid: session_guid, stop, pinger: Some(pinger), gdi_name,