mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
5cb233e1c9
Six independent paths reach analysis_cache::seed_from_bytes for the same
track_id during a cache-miss play under LUFS:
- track_download_task legacy stream capture-complete
- ranged_download_task HTTP buffer full
- audio_preload bytes-ready
- psysonic-local file-read complete
- spawn_analysis_seed_from_in_memory_bytes (gapless reuse / preloaded /
stream cache)
- analysis_enqueue_seed_from_url (frontend backfill triggered by
refresh:miss while a playback path is mid-seed)
Whenever a track is streamed for the first time with loudness on, two of
these fire in parallel and Symphonia + EBU R128 decode the same buffer
twice — ~30 s of duplicate CPU per track, plus a redundant SQLite write
of an identical row. Reproduced on multiple cache-miss tracks (KjP… ranged
+ backfill, mPdz… preloaded bytes + backfill).
Coordinate at the funnel:
- New SeedFromBytesOutcome::SkippedConcurrent variant.
- SEEDS_IN_FLIGHT static (Mutex<HashSet<String>>) tracks track_ids whose
analysis is currently running. First caller into seed_from_bytes
wins the slot via HashSet::insert; later callers return SkippedConcurrent
immediately and let the winner publish the analysis:waveform-updated
event.
- SeedInFlightGuard (RAII) releases the slot on every exit path including
panics, so a crashing analysis cannot leak the marker.
- enqueue_analysis_seed in lib.rs distinguishes the SkippedConcurrent log
from the genuine "seed result" log so the frontend backfill case
(when it arrives second) doesn't read like a backfill failure.
Existing call-site match arms in audio.rs already use Ok(_) => {} for
non-Upserted outcomes — no changes needed there. enqueue_analysis_seed in
lib.rs already gates the waveform-updated emit on outcome == Upserted, so
the new variant is silent there too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>