Home

dev / openkara

publicthedavidweng/OpenKara· sync paused
Code Branches Pull requestsIssuesInsights
main
Home Code PRsIssues

#382 Streaming reconnect leaves the previous fetch-event listener thread alive

closed

Opened by dev · yesterday

devopened this issueAuthor· yesterday

Line references are pinned to main at bac2f765.

Pre-existing on main. The code moved into services::track_load::streaming in #369 without behavioral change, so do not bisect to that PR.

Where

src-tauri/src/services/track_load/streaming.rs L38-71 (spawn_fetch_event_listener) and L86-198 (attempt_reconnect).

What goes wrong

Each streaming source gets a listener thread that loops over that source's fetch-event receiver:

// L44-70
std::thread::spawn(move || {
    let _pin = cache_pin_guard;
    let song_id = ctx.song_id.clone();
    for event in fetch_event_rx {
        match event { /* reconnect / fallback / reconnect */ }
    }
});

Recovery runs inline on that thread. On success, a listener is spawned for the new receiver and the source is swapped:

// L164-180
match fetch_event_rx {
    Some(rx) => spawn_fetch_event_listener(ctx.clone(), cache_pin_guard, rx, RECONNECTED_FETCH),
    None => { if let Some(pin) = cache_pin_guard { spawn_cache_pin_hold(pin, guard); } }
}

let _ = ctx.send(PlaybackCommand::ReplaceStreamingSource { /* ... */ });

Nothing stops the old listener. attempt_reconnect returns, and the old thread goes back to for event in fetch_event_rx on the receiver belonging to the source that was just replaced. The full-file fallback at L57-60 has the same shape: install_decoded replaces the streaming source, then the listener resumes waiting on the old channel.

Two consequences:

  • Redundant recovery. As long as the replaced fetcher still holds its sender and emits, the stale listener can fire another attempt_reconnect or another PLAYBACK_ERROR_EVENT (L185-196) for a source that is no longer installed. The staleness guard (guard.predicate(), L154) rejects work for superseded requests, but the stale listener belongs to the same request id as the live one, so it is not filtered out on that basis.
  • Thread and cache-pin accumulation. Each successful reconnect adds a listener thread, and each holds its cache pin alive through let _pin = cache_pin_guard (L45), deferring eviction of a cache entry nothing is reading.

Conditions

A remote streaming track that reconnects — repeated transient failures or an expired download URL — and any case where the replaced fetcher continues to emit events afterwards.

Effect

Reconnect storms and log noise attributable to a dead source, and thread/cache-pin growth proportional to reconnect count over a long session.

Suggested resolution (not applied)

Give the listener an explicit stop signal (or drop the old receiver/sender pair as part of the source swap) so exactly one listener is live per installed source.

Not fixing here

Filed for tracking only.

Sign in to comment.

Linked pull requests

No linked pull requests yet.