From 787756e68b9c36cac5953c3f3ca74b4ca4748400 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Tue, 28 Jul 2026 15:27:11 +0200 Subject: [PATCH] test(vdisplay/mutter): the on-glass lever for the GNOME backend, and what it took to see the leak MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `live_mutter_create_drop`, `#[ignore]`d in the same convention as the Windows backend's live tests: the real four-step handshake, hold, then drop the keepalive and let the RAII teardown revert it. Verified against gnome-shell on 192.168.1.21 — node_id came back in ~5 ms, preferred_mode as asked, and the topology reverted. Two things that would otherwise be re-derived by whoever validates this next, both of which cost a wrong answer here first. The virtual monitor does NOT appear in DisplayConfig's GetCurrentState just because `create` succeeded — its size follows the PipeWire negotiation, so without a consumer attached there is no monitor to census. A before/after monitor diff therefore proves nothing about this backend, which is the trap the first attempt fell into. And a short-lived process cannot exhibit the abandoned-thread leak at all: exiting drops the D-Bus connection, which makes Mutter tear the session down no matter which code is running. Reproducing it needs the process held alive past the failed create, and the observable is the live RemoteDesktop session object, not the monitor: `gdbus introspect -d org.gnome.Mutter.RemoteDesktop -o /org/gnome/Mutter/RemoteDesktop` lists a `node Session` child per live session. With a 1 ms create timeout forced and the process held 20 s, that probe reads 0 with b89a36a6 and 1 without it — the orphan thread parked on a live session. Both read 0 after exit, which is precisely why this only ever bit a long-running host. Co-Authored-By: Claude Opus 5 (1M context) --- .../pf-vdisplay/src/vdisplay/linux/mutter.rs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/crates/pf-vdisplay/src/vdisplay/linux/mutter.rs b/crates/pf-vdisplay/src/vdisplay/linux/mutter.rs index 8a7697c4..1f7c8f4e 100644 --- a/crates/pf-vdisplay/src/vdisplay/linux/mutter.rs +++ b/crates/pf-vdisplay/src/vdisplay/linux/mutter.rs @@ -1370,4 +1370,50 @@ mod tests { fn nothing_new_is_nothing_to_pick() { assert!(pick_virtual(&[], M).is_none()); } + + /// Live GNOME round trip — the on-glass lever for this backend, same convention as the Windows + /// backend's `live_create_drop`. Exercises the real four-step handshake end to end: create the + /// virtual monitor, hold it, then drop the keepalive and let the RAII teardown revert it. + /// + /// Needs a running `gnome-shell` on the session bus. Run it as: + /// ```text + /// PUNKTFUNK_MUTTER_VIRTUAL_PRIMARY=0 \ + /// cargo test -p pf-vdisplay -- --ignored --nocapture live_mutter_create_drop + /// ``` + /// **Set that variable** unless you mean to exercise the exclusive topology: without it the + /// default resolves to `Exclusive`, which disables the box's physical heads for the duration + /// (they come back on teardown — that is what this test also proves, but on a box you are + /// sitting at, do it deliberately). + #[test] + #[ignore = "needs a live gnome-shell on the session bus; run with --ignored"] + fn live_mutter_create_drop() { + use super::{MutterDisplay, VirtualDisplay}; + + let mode = Mode { + width: 1920, + height: 1080, + refresh_hz: 60, + }; + let mut vd = MutterDisplay::new().expect("construct the Mutter backend"); + let started = std::time::Instant::now(); + let out = vd.create(mode).expect("create the Mutter virtual monitor"); + println!( + "created: node_id={} preferred={:?} in {:?}", + out.node_id, + out.preferred_mode, + started.elapsed() + ); + assert!(out.node_id > 0, "a real PipeWire node id"); + assert_eq!( + out.preferred_mode, + Some((mode.width, mode.height, mode.refresh_hz)) + ); + + std::thread::sleep(std::time::Duration::from_secs(3)); + drop(out); + // The keepalive's Drop only SIGNALS the thread; give it more than one 200 ms tick to run + // the Stop + topology revert before the harness exits and takes the process with it. + std::thread::sleep(std::time::Duration::from_secs(2)); + println!("dropped — gnome-shell should have removed the monitor and reverted the topology"); + } }