mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
fix(analysis): dedupe concurrent seed_from_bytes for the same track_id
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>
This commit is contained in:
committed by
Maxim Isaev
parent
2d222e2691
commit
5cb233e1c9
+18
-6
@@ -1406,12 +1406,24 @@ fn enqueue_analysis_seed(app: &tauri::AppHandle, track_id: &str, bytes: &[u8]) -
|
||||
.try_state::<analysis_cache::AnalysisCache>()
|
||||
.and_then(|cache| cache.get_latest_loudness_for_track(track_id).ok().flatten())
|
||||
.is_some();
|
||||
crate::app_deprintln!(
|
||||
"[analysis] seed result track_id={} bytes={} has_loudness={}",
|
||||
track_id,
|
||||
bytes.len(),
|
||||
has_loudness
|
||||
);
|
||||
// SkippedConcurrent gets logged with the actual outcome so the line doesn't
|
||||
// read "seed result has_loudness=false" when in fact another path is mid-seed
|
||||
// and will publish the row in seconds.
|
||||
if outcome == analysis_cache::SeedFromBytesOutcome::SkippedConcurrent {
|
||||
crate::app_deprintln!(
|
||||
"[analysis] seed deferred to in-flight peer track_id={} bytes={} has_loudness_now={}",
|
||||
track_id,
|
||||
bytes.len(),
|
||||
has_loudness
|
||||
);
|
||||
} else {
|
||||
crate::app_deprintln!(
|
||||
"[analysis] seed result track_id={} bytes={} has_loudness={}",
|
||||
track_id,
|
||||
bytes.len(),
|
||||
has_loudness
|
||||
);
|
||||
}
|
||||
Ok(has_loudness)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user