fix(host): survive Bazzite's SDDM session supervisor in the gamescope takeover
Diagnosed live on the .181 Bazzite F44 box (couldn't connect at all; field reports of streams dying after 30 s-5 min): Bazzite autologs into game mode via SDDM with Relogin=true, so the moment the managed takeover stops gamescope-session-plus@<client>, SDDM logs back in and restarts it within the same second. The resurrected autologin session then fights our transient session-plus over the Steam single instance and the GPU for the whole stream: its wrapper relaunches gamescope every ~7 s (each one missing the wrapper's hard 5 s readiness window on a slow NVIDIA init), the churn SIGSEGVs gamescopes, and eventually the streaming gamescope dies with it. Meanwhile a client that gave up left the pipeline-rebuild retry loop SIGKILLing and relaunching the box's Steam session for up to ~6 more minutes. - stop_autologin_sessions: runtime-mask each autologin unit before the SIGKILL stop, so no supervisor can restart it underneath the stream; match every loaded instance (the unit flaps through activating/failed mid-churn). Every restore path unmasks unconditionally (including the desktop-active early return), and --runtime keeps the mask in tmpfs so a reboot clears it. - launch_session: supervise the transient unit while polling for the node — the session-plus wrapper kill -9s a gamescope that missed its 5 s readiness handshake and exits 1, so relaunch it (after a short driver-settle cooldown) instead of waiting the rest of the 45 s on a corpse. - build_pipeline_with_retry: abort between attempts once the session's QUIC connection is closed — no more minutes of Steam churn for a departed client. Validated live on .181: cold-boot connect streams 2059 frames/45 s (p50 5.1 ms), zero SDDM resurrections while masked, TV session restored+unmasked on disconnect, warm same-mode reconnect reuses the session (866 frames/15 s). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3096,7 +3096,7 @@ fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
let _idd_setup_guard = (plan.capture == crate::session_plan::CaptureBackend::IddPush)
|
||||
.then(|| crate::vdisplay::manager::vdm().begin_idd_setup(stop.clone()));
|
||||
let (mut capturer, mut enc, mut frame, mut interval, mut cur_node_id) =
|
||||
build_pipeline_with_retry(&mut vd, mode, bitrate_kbps, bit_depth, plan, &quit)?;
|
||||
build_pipeline_with_retry(&mut vd, mode, bitrate_kbps, bit_depth, plan, &quit, &stop)?;
|
||||
// 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);
|
||||
@@ -3280,6 +3280,7 @@ fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
bit_depth,
|
||||
plan,
|
||||
&quit,
|
||||
&stop,
|
||||
)?;
|
||||
Ok((new_vd, pipe))
|
||||
})();
|
||||
@@ -3486,6 +3487,7 @@ fn virtual_stream(ctx: SessionContext) -> Result<()> {
|
||||
bit_depth,
|
||||
plan,
|
||||
&quit,
|
||||
&stop,
|
||||
) {
|
||||
Ok(p) => break p,
|
||||
Err(e2) => {
|
||||
@@ -3793,6 +3795,7 @@ fn build_pipeline_with_retry(
|
||||
bit_depth: u8,
|
||||
plan: crate::session_plan::SessionPlan,
|
||||
quit: &Arc<AtomicBool>,
|
||||
stop: &Arc<AtomicBool>,
|
||||
) -> Result<Pipeline> {
|
||||
// ~10s first-frame wait per attempt. 8 gives a ~90s budget for the SLOW case: a host-managed
|
||||
// gamescope session cold-starting Steam Big Picture (the SteamOS/Bazzite takeover) can take
|
||||
@@ -3819,6 +3822,17 @@ fn build_pipeline_with_retry(
|
||||
const MAX_ATTEMPTS: u32 = 8;
|
||||
let mut backoff = std::time::Duration::from_millis(500);
|
||||
for attempt in 1..=MAX_ATTEMPTS {
|
||||
// The client is gone (connection closed → `stop`): every further attempt only churns the
|
||||
// box for a session no one is watching — on a Bazzite takeover that means SIGKILLing and
|
||||
// relaunching the box's Steam session once per attempt for minutes (the .181 storm
|
||||
// 2026-07-07). One in-flight attempt can still overhang; this bounds the damage to it.
|
||||
if attempt > 1 && stop.load(Ordering::SeqCst) {
|
||||
anyhow::bail!(
|
||||
"session ended (client disconnected) during pipeline build — aborting retries \
|
||||
after {} attempt(s)",
|
||||
attempt - 1
|
||||
);
|
||||
}
|
||||
match build_pipeline(vd, mode, bitrate_kbps, bit_depth, plan, quit) {
|
||||
Ok(pipe) => {
|
||||
if attempt > 1 {
|
||||
|
||||
Reference in New Issue
Block a user