fix(F4): clear real_sink.name when the adopted sink departs
Codex audit of the F1-F6 sweep flagged this. The fallback path in
`try_capture_real_sink` adopts the first non-processed Audio/Sink
when no real sink is known; if that sink later disconnects (USB
DAC pulled, Bluetooth peer drops), `on_global_remove`'s
`sinks_by_name.retain` was clearing `s.real_sink.node_id` but
leaving `s.real_sink.name` set to the departed name. Symptoms:
- `apply_pending_routes` then logs "target sink not yet on
registry" for every bypass route and queues forever — `name`
no longer resolves through `sinks_by_name`.
- `adopt_new_real_sink` from the metadata listener would
normally rescue this, but WP only re-fires `default.audio.sink`
on actual changes; if the user's default is unchanged in WP's
view (because the departed sink wasn't WP's pick), no event
arrives.
The retain-callback now clears both `name` and `node_id` when the
removed node's name matches `real_sink.name`. The F4 fallback will
then pick a replacement from the next non-processed Audio/Sink the
registry surfaces, or a fresh metadata event will set a specific
choice — either path recovers cleanly.
Codex's other finding (theoretical duplicate-link creation in
multi-channel apply_pending_routes when the link listener lags
behind the drain timer within a single tick) is real-but-unlikely:
the listener fires within microseconds on the same event loop;
drain ticks are 50 ms apart, so the listener always catches up
before the next drain. The unchanged-target gap-mitigation from
`5c769a1` also reduces exposure — most ReevaluateAll passes don't
hit the create loop at all now. Filed as a "watch but don't fix"
note inline in the routing follow-up memory.
190 tests pass; clippy clean.
This commit is contained in:
parent
5c769a1226
commit
ec49206660
1 changed files with 14 additions and 1 deletions
|
|
@ -1485,7 +1485,20 @@ impl RoutingState {
|
|||
if id == node_id {
|
||||
tracing::debug!(node_id, name, "real sink removed from registry");
|
||||
let mut s = self.daemon.lock();
|
||||
if s.real_sink.node_id == Some(node_id) {
|
||||
// Clear BOTH name and node_id when the departing
|
||||
// sink is our preferred real sink. Just nulling
|
||||
// node_id (the previous behaviour) left the name
|
||||
// pinned to a sink that no longer exists, so
|
||||
// `apply_pending_routes` would queue every bypass
|
||||
// route forever against a stale target name. The
|
||||
// F4 fallback or a fresh `default.audio.sink` event
|
||||
// can then pick a replacement.
|
||||
if s.real_sink.name.as_deref() == Some(name.as_str()) {
|
||||
s.real_sink.name = None;
|
||||
s.real_sink.node_id = None;
|
||||
} else if s.real_sink.node_id == Some(node_id) {
|
||||
// Defensive: id matched but name didn't (sinks
|
||||
// shouldn't double-register). Null the id.
|
||||
s.real_sink.node_id = None;
|
||||
}
|
||||
false
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue