fix(host/linux): stop burning retry budget on doomed pipeline builds
Two session-transition stalls found live on a SteamOS Deck host, one cause: the pipeline retry loop couldn't tell a wait-it-out failure from a retry-now-and-it-works one. - Gamescope first-frame race: a PipeWire stream connected while gamescope re-initializes its headless takeover negotiates a format, reaches Streaming, and never receives a buffer — while a fresh connect delivers within ~0.5 s. Every gamescope bring-up ate the full 10 s first-frame budget on attempt 1 (17 s bring-ups; KWin: 1.2 s). The retry loop's FIRST attempt now waits 2.5 s (Capturer::next_frame_within), so the reconnect that fixes the race happens at ~3 s. Later attempts keep the patient 10 s — the documented 30-60 s Big Picture cold start still fits the budget. - Capture-loss rebuild vs session switch: the rebuild loop re-detects the active session between build_pipeline_with_retry calls, but each call burned 8 attempts (~13 s) against a compositor that no longer exists (a Desktop→Gaming switch spent 13 of its 27 s retrying gone-KWin). The capture-loss path now passes max_attempts=2, turning the outer loop into ~1 s detect-and-retry cycles that follow the box to the new session. The resize path's direct build_pipeline call keeps the default budget (no retry wrapper there to absorb an early bail). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,16 @@ use pf_frame::DmabufFrame;
|
||||
pub trait Capturer: Send {
|
||||
fn next_frame(&mut self) -> Result<CapturedFrame>;
|
||||
|
||||
/// [`next_frame`](Self::next_frame) with a caller-chosen first-frame budget instead of the
|
||||
/// backend's default. The pipeline retry loop shortens its FIRST attempt's wait: a PipeWire
|
||||
/// stream connected while gamescope re-inits its headless takeover can negotiate a format,
|
||||
/// reach `Streaming`, and still never receive a buffer — a fresh connect then delivers within
|
||||
/// ~0.5 s, so waiting out the full default budget on a doomed stream just delays the retry
|
||||
/// that fixes it. Backends without an internal wait budget ignore it (the default delegates).
|
||||
fn next_frame_within(&mut self, _budget: std::time::Duration) -> Result<CapturedFrame> {
|
||||
self.next_frame()
|
||||
}
|
||||
|
||||
/// Non-blocking: the freshest frame available since the last call, or `None` if none has
|
||||
/// arrived (the caller reuses its last frame to hold a steady output rate). The default
|
||||
/// just produces a frame each call — fine for instant synthetic sources; the portal
|
||||
|
||||
Reference in New Issue
Block a user