From fe61597d92e441d39833f2b48b72abae568813e5 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Thu, 25 Jun 2026 19:58:02 +0000 Subject: [PATCH] =?UTF-8?q?refactor(windows-host):=20=C2=A72.5=20step=203?= =?UTF-8?q?=20=E2=80=94=20isolate=20the=20IDD-push=20preempt=20into=20the?= =?UTF-8?q?=20manager?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The last two virtual-display globals lived in punktfunk1: IDD_SETUP_LOCK (serialize IDD-push setup against a reconnect flood) + IDD_SESSION_STOP (the prior session's stop flag, signalled + waited-on so a reconnect preempts the stale session cleanly). Both move onto VirtualDisplayManager as fields, behind one `vdm().begin_idd_setup(stop)` method that locks the setup gate, registers this session's stop while signalling the prior one, waits for the monitor to release, and hands back the setup guard the session holds across the pipeline build. punktfunk1 no longer reaches into vdisplay internals for the preempt — it just calls the manager and holds the guard. Behaviour-identical (same lock/signal/wait order, same guard lifetime). Completes §2.5's "delete the smeared globals": CURRENT_MON_GEN/MON_GEN/MGR x2/IDD_PERSIST/IDD_SETUP_LOCK/ IDD_SESSION_STOP are all gone, replaced by the one OnceLock VirtualDisplayManager with a typed OwnedHandle device. Box build to follow; on-glass reconnect-leak test pending. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/punktfunk-host/src/punktfunk1.rs | 51 +++---------------- .../src/vdisplay/windows/manager.rs | 28 ++++++++++ 2 files changed, 36 insertions(+), 43 deletions(-) diff --git a/crates/punktfunk-host/src/punktfunk1.rs b/crates/punktfunk-host/src/punktfunk1.rs index 2eebf59..29ffe81 100644 --- a/crates/punktfunk-host/src/punktfunk1.rs +++ b/crates/punktfunk-host/src/punktfunk1.rs @@ -2141,30 +2141,6 @@ fn session_watcher_loop(tx: std::sync::mpsc::Sender, stop: Arc>> = std::sync::Mutex::new(None); - -/// Serializes IDD-push session SETUP (preempt + monitor create + first frame). Held across setup, -/// released before the encode loop — so a reconnect FLOOD can never run concurrent monitor -/// create/teardown (the churn that fails the ADD IOCTL and wedges the driver). Each session finishes -/// setup before the next acquires this and preempts it, by which point the preempted session is in its -/// encode loop and releases its monitor promptly. -#[cfg(target_os = "windows")] -static IDD_SETUP_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); - /// All per-session inputs for [`virtual_stream`] / [`virtual_stream_relay`], bundled so the session entry /// is one moved value instead of a 13-positional-argument `#[allow(too_many_arguments)]` signature /// (Goal-1 stage 4, plan §2.4). Everything is **owned** — the receivers move in (`virtual_stream` is their @@ -2240,31 +2216,20 @@ fn virtual_stream(ctx: SessionContext) -> Result<()> { bit_depth, "punktfunk/1 virtual display" ); - // IDD-push reconnect preempt: a fresh connection means the prior client is gone. Hold IDD_SETUP_LOCK - // across the preempt + pipeline build so a reconnect FLOOD can't run concurrent monitor - // create/teardown. Then STOP the prior session (it ends cleanly while its monitor still composites - // frames) and WAIT for it to release its monitor, before building a FRESH one — instead of the - // driver-churning teardown of a monitor under a still-live session. Register THIS session's stop so - // the next reconnect preempts it. + // IDD-push reconnect preempt (the dance now lives in the manager, Goal-1 §2.5): serialize setup so a + // reconnect FLOOD can't run concurrent monitor create/teardown, STOP the prior session + WAIT for it + // to release its monitor (instead of tearing a monitor out from under a still-live session), and + // register THIS session's stop. The returned guard holds the setup lock across the pipeline build; + // dropping it lets the next reconnect begin (and preempt us). #[cfg(target_os = "windows")] - let idd_push_session = plan.capture == crate::session_plan::CaptureBackend::IddPush; - #[cfg(target_os = "windows")] - let idd_setup_guard = idd_push_session.then(|| IDD_SETUP_LOCK.lock().unwrap()); - #[cfg(target_os = "windows")] - if idd_push_session { - let prev = IDD_SESSION_STOP.lock().unwrap().replace(stop.clone()); - if let Some(prev_stop) = prev { - prev_stop.store(true, Ordering::SeqCst); - crate::vdisplay::manager::vdm() - .wait_for_monitor_released(std::time::Duration::from_secs(3)); - } - } + let _idd_setup_guard = (plan.capture == crate::session_plan::CaptureBackend::IddPush) + .then(|| crate::vdisplay::manager::vdm().begin_idd_setup(stop.clone())); let mut vd = crate::vdisplay::open(compositor)?; let (mut capturer, mut enc, mut frame, mut interval) = build_pipeline_with_retry(&mut vd, mode, bitrate_kbps, bit_depth, plan)?; // Setup done — release the IDD-push setup lock so the next reconnect can begin (and preempt us). #[cfg(target_os = "windows")] - drop(idd_setup_guard); + drop(_idd_setup_guard); // Windows single-process DDA path (PUNKTFUNK_NO_WGC=1): the SudoVDA virtual display, isolated as the // SOLE active output, goes into fullscreen independent-flip (one plane on one display) which Desktop diff --git a/crates/punktfunk-host/src/vdisplay/windows/manager.rs b/crates/punktfunk-host/src/vdisplay/windows/manager.rs index f0efb22..077e4e9 100644 --- a/crates/punktfunk-host/src/vdisplay/windows/manager.rs +++ b/crates/punktfunk-host/src/vdisplay/windows/manager.rs @@ -120,6 +120,13 @@ pub(crate) struct VirtualDisplayManager { /// Monotonic lease-generation counter (was the `MON_GEN` global). gen: AtomicU64, state: Mutex, + /// Serializes IDD-push session SETUP (preempt + monitor create) so a reconnect flood can't run + /// concurrent monitor create/teardown — held by the session across the pipeline build (was the + /// `IDD_SETUP_LOCK` global in `punktfunk1`). + setup_lock: Mutex<()>, + /// The current IDD-push session's stop flag; a new connection signals the prior one to release its + /// monitor before the fresh one is created (was the `IDD_SESSION_STOP` global in `punktfunk1`). + idd_session_stop: Mutex>>, } static VDM: OnceLock = OnceLock::new(); @@ -133,6 +140,8 @@ pub(crate) fn init(driver: Box) -> &'static VirtualDisplayMa watchdog_s: AtomicU32::new(3), gen: AtomicU64::new(1), state: Mutex::new(MgrState::Idle), + setup_lock: Mutex::new(()), + idd_session_stop: Mutex::new(None), }) } @@ -390,6 +399,25 @@ impl VirtualDisplayManager { }; } + /// Begin an IDD-push session setup (Goal-1 §2.5 — was the `IDD_SETUP_LOCK` / `IDD_SESSION_STOP` / + /// `wait_for_monitor_released` dance smeared across `punktfunk1`). Serializes via the setup lock, + /// registers THIS session's stop flag while signalling the PRIOR IDD-push session to stop, and waits + /// for it to release its monitor — so a reconnect (whose reused IddCx swap-chain is dead) preempts the + /// stale session cleanly before a fresh monitor is created. Returns the setup guard; the caller holds + /// it across the pipeline build, then drops it so the next reconnect can begin (and preempt this one). + pub(crate) fn begin_idd_setup( + &'static self, + stop: Arc, + ) -> std::sync::MutexGuard<'static, ()> { + let guard = self.setup_lock.lock().unwrap(); + let prev = self.idd_session_stop.lock().unwrap().replace(stop); + if let Some(prev_stop) = prev { + prev_stop.store(true, Ordering::SeqCst); + self.wait_for_monitor_released(Duration::from_secs(3)); + } + guard + } + /// Wait (up to `timeout`) for the active monitor to be RELEASED (the MGR is no longer `Active`). /// Used by the IDD-push reconnect preempt: after signalling the old session to stop, wait here so it /// tears its monitor down cleanly before we acquire a fresh one.