feat(vdisplay): harden keep-alive reconnect — same-client preempt, quit-skips-linger, configurable idle
On-glass testing (Test 2, KWin .116) surfaced that a reconnect within the QUIC idle-timeout window (~8s) lands on a fresh SECOND display instead of reusing the kept one: the old session was still Active (not yet Lingering), so the registry's keep-alive reuse (which only matches Lingering) skipped it and the old session kept streaming to nobody. Three fixes: #3 Same-client reconnect preempt (the real fix): admission::preempt_same_identity() lists a reconnecting client's OWN still-live session(s) (same cert fingerprint); serve_session signals their stop + waits the release grace BEFORE acquiring, so the zombie tears down → its display lingers → the reconnect REUSES it instead of making a second. Implements the "preempts downstream" the admission docs already promised. Independent of the mode_conflict policy; the pure core (same_identity_stops) is unit-tested. #2 Deliberate quit skips linger: a client that deliberately disconnects closes the QUIC connection with QUIT_CLOSE_CODE (0x51, shared in core::quic); the host reads the ApplicationClosed reason and tears the display down immediately (registry release() gained force_immediate → Linger::Immediate; multi-session-safe via the pure lifecycle machine), while a bare disconnect still lingers for reconnect. Threaded via a session quit flag → the DisplayLease. NativeClient::disconnect_quit() + punktfunk-probe --quit drive it; GameStream (Quit App / h_cancel) is a documented follow-up. #1 Configurable disconnect-detection latency: the QUIC control-connection idle timeout (stream_transport, 8s default) is host-tunable via --idle-timeout-ms / PUNKTFUNK_IDLE_TIMEOUT_MS, clamped >=1s with a keep-alive that scales to it so a live session never false-closes. Default unchanged (8s stays load-bearing for the Windows IDD-push reconnect flow). Workspace check + 63 core / 215 host / 47 vdisplay tests green; clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,10 @@ struct Args {
|
||||
/// `--rich-input-test` — drive the DualSense touchpad + motion over 0xCC (host needs
|
||||
/// `PUNKTFUNK_GAMEPAD=dualsense`); also logs the 0xCD HID-output feedback that comes back.
|
||||
rich_input_test: bool,
|
||||
/// `--quit` — close the connection with the deliberate-quit code (`QUIT_CLOSE_CODE`) at end of
|
||||
/// stream, so the host tears its virtual display down immediately (skips keep-alive linger). A
|
||||
/// bare exit closes with code 0 → the host lingers for a reconnect. Tests the #2 quit path.
|
||||
quit: bool,
|
||||
pin: Option<[u8; 32]>,
|
||||
/// `--remode WxHxFPS:SECS` — request this mode SECS seconds into the stream.
|
||||
remode: Option<(Mode, u32)>,
|
||||
@@ -211,6 +215,7 @@ fn parse_args() -> Args {
|
||||
mic_burst: argv.iter().any(|a| a == "--mic-burst"),
|
||||
touch_test: argv.iter().any(|a| a == "--touch-test"),
|
||||
rich_input_test: argv.iter().any(|a| a == "--rich-input-test"),
|
||||
quit: argv.iter().any(|a| a == "--quit"),
|
||||
pin,
|
||||
remode,
|
||||
pair: get("--pair").map(String::from),
|
||||
@@ -1208,7 +1213,14 @@ async fn session(args: Args) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
conn.close(0u32.into(), b"done");
|
||||
// `--quit` closes with the deliberate-quit code so the host skips the keep-alive linger; a normal
|
||||
// exit uses code 0 (an unwanted-disconnect close → the host lingers for a reconnect).
|
||||
let close_code = if args.quit {
|
||||
punktfunk_core::quic::QUIT_CLOSE_CODE
|
||||
} else {
|
||||
0
|
||||
};
|
||||
conn.close(close_code.into(), b"done");
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user