Fix AA Background Processing to not Monopolize CPU#3873
Merged
chrisuthe merged 8 commits intoMay 11, 2026
Conversation
Add a one-second minimum interval between consecutive PCM chunk dispatches in both the live and background paths. Each chunk represents one audio-second, so pacing at 1.0s dispatches at real-time, naturally bounding analysis CPU to "what providers need to keep up with playback" without explicit measurement or per-provider throttling. The live path's producer paces automatically via existing chunk_callback backpressure; the audio buffer hovers near ready_threshold rather than racing ahead. Background scan duration becomes the sum of track durations; CONF_BACKGROUND_SCAN_CONCURRENCY remains as the speedup knob for capable hardware.
Add min_interval and max_interval parameters to _chunk_worker, _run_background_streaming_for_track, and _run_background_streaming_inner. The two production call sites now pass values explicitly, demonstrating the per-call pattern. min_interval is the floor on wall-seconds between dispatches (i.e. the speed ceiling); max_interval is the ceiling on wall-seconds between dispatches (the speed floor) and is reserved for future adaptive logic. Behavior is unchanged: both call sites pass REAL_TIME_PACE_INTERVAL_SECONDS for both bounds. The API surface is in place for future config-driven or adaptive pacing without further structural changes.
…u_count DEFAULT_BACKGROUND_SCAN_CONCURRENCY now scales with the host's CPU count via _default_background_scan_concurrency(): 1 on hosts with <8 cores, 2 on 8–15 cores, 4 on 16+. Mirrors the pattern used for buffer size selection by total RAM (streams/constants.py _get_default_buffer_size). CONF_BACKGROUND_SCAN_CONCURRENCY remains the user override; only the default value changes. The existing _get_scan_concurrency() clamping to [1, 8] is unaffected. Update test_get_scan_concurrency_returns_default_on_unset to assert against DEFAULT_BACKGROUND_SCAN_CONCURRENCY directly rather than the literal 1, so it passes on every host regardless of cpu_count.
…adroom Pacing at exactly 1.0s left the live audio buffer running dead-even with playback rate — no margin if the producer hiccups. 0.9s gives ffmpeg roughly 11% headroom to keep the buffer slightly ahead of the player, while still being well under the 1s per-chunk timeout that bounds provider work. Background scans also complete ~11% faster as a side effect.
…e CEILING as per-chunk timeout Replace REAL_TIME_PACE_INTERVAL_SECONDS with two related constants: REAL_TIME_PACE_INTERVAL_SECONDS_FLOOR = 0.5 (fastest dispatch) REAL_TIME_PACE_INTERVAL_SECONDS_CEILING = 1.0 (slowest dispatch / timeout) The floor is the fastest pace we'll ever dispatch at (when providers can keep up); the ceiling is now both the slowest pace and the per-chunk processing timeout that evicts unresponsive providers. This collapses two previously-separate concepts (interval and CHUNK_PROCESS_TIMEOUT_SECONDS) into one parameterized bound and eliminates the redundant constant. _distribute_chunk now accepts max_interval as a parameter so callers can control the eviction threshold per session. The live and background dispatchers thread their max_interval through to _distribute_chunk so patching the ceiling constant (or passing different values per call) propagates correctly. The live _on_chunk queue.put timeout also uses the ceiling directly, since both are bounded by the same "how long do we wait on one chunk" semantic.
…ime throughput The 0.5s floor still left half a wall-second of idle between dispatches when providers complete quickly. 0.25s gives capable hardware up to 4× real-time throughput on chunks the providers can actually keep up with, while the 1.0s ceiling still bounds slow-provider eviction.
The pacing-params commit added min_interval and max_interval kwargs to _run_background_streaming_for_track and _run_background_streaming_inner. Four test stubs that monkeypatch those methods don't accept the kwargs and were raising TypeError on call. Adding **_kwargs: object to each stub absorbs the new arguments without caring about their values — matches the existing _hang stub pattern in the same file. Resolves three CI failures: test_run_background_scan_uses_union_candidate_query test_run_background_scan_concurrency_semaphore test_background_streaming_cancellation_cleans_up
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Currently background processing goes As Fast As Possible, using as much CPU as it can while it's running (default midnight to 4 AM). This PR shifts the AFAP to be throttled to at MOST 4x real time, also enforcing that for real-time analysis during playback.
The background process will run one song at a time unless 8+ cores of CPU are detected, then it will run 2 songs until 16+ are detected then it will run 4.