Fix stale DLNA SessionEnded tearing down the next cast session (CAST-4) #5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/cast-4-dlna-stale-session-ended"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes CAST-4.
Root cause
Two compounding bugs, both confirmed against the real code:
daemon.rs'sStopCasthandler only bumpedstart_generationwhen it cancelled an in-flight start (
self.starting). When it toredown an already-installed session (the ordinary "user clicks Stop"
path), the generation was left unchanged. A stop, then a quick start,
kept the exact same generation number throughout.
dlna_mirror.rs's bus-watcher thread (spawned via barestd::thread::spawnaround what's nowstart()'sbus_watcherblock) had its
JoinHandlediscarded — not stored onDlnaMirrorSession, never joined instop(). Sostop()couldreturn before that thread noticed the pipeline reached
Nullandsent its
SessionEnded { generation }, an unbounded (if usuallyshort) time later.
Combined: a stop → start on DLNA within a few seconds could let the old
session's late
SessionEnded— carrying the unchanged generation —either cancel the new session's in-flight start, or
active_session.take()and tear down the new session outright, right after it started
successfully mirroring.
Fix (both parts, per the issue's own analysis)
breadcastd/src/daemon.rs:StopCastnow bumpsstart_generationwhenever it takes an installed
active_session, mirroring what theSessionEndedhandler already does when a session ends on its own.This alone makes the generation check correctly reject any late
message from a superseded session, since the generation is captured by
value in the watcher thread at spawn time.
breadcastd/src/dlna_mirror.rs: the bus-watcher thread'sJoinHandleis now stored on
DlnaMirrorSessionand joined (bounded to 5s) instop(), right after the pipeline is set toNull(which is whatunblocks it) — the same pattern
CastMirrorSession::stop()alreadyuses for
frame_pump. This closes the resource-leak/non-determinismgap and brings DLNA to parity with the Cast path, so
stop()is a realsynchronization point again.
Testing
Added a deterministic regression test in
daemon.rs(
stop_cast_on_an_installed_session_bumps_generation_so_a_stale_watcher_thread_cannot_kill_the_next_session)that drives the exact stop → install-new-session → stale-
SessionEndedsequence directly through
Daemon::handle, using a#[cfg(test)]-onlyActiveSession::Stubvariant (a real session needs a portal capturesession + GStreamer pipeline, so it can't be constructed in a unit test —
same constraint the existing in-flight-start race tests already work
around). No sleeps, no real threads, nothing timing-dependent.
Pull request closed