Fix Docker boot hang when Spotify is the configured primary source.
Avoid blocking Spotify auth probes during gunicorn worker import and add a request timeout to the auth probe client so unreachable API calls cannot stall startup indefinitely. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
36986e4668
commit
9b18e99419
6 changed files with 48 additions and 4 deletions
|
|
@ -139,6 +139,7 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
|||
ENV PYTHONPATH=/app
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV DATABASE_PATH=/app/data/music_library.db
|
||||
ENV SOULSYNC_CONFIG_PATH=/app/config/config.json
|
||||
ENV PUID=1000
|
||||
ENV PGID=1000
|
||||
ENV UMASK=022
|
||||
|
|
|
|||
|
|
@ -352,10 +352,21 @@ def get_hydrabase_client(allow_fallback: bool = True, require_enabled: bool = Tr
|
|||
return None
|
||||
|
||||
|
||||
def get_configured_primary_source() -> str:
|
||||
"""Return metadata.fallback_source as stored in config, without runtime downgrade.
|
||||
|
||||
Unlike get_primary_source(), this never probes Spotify auth. Use at boot and
|
||||
anywhere blocking network I/O must be avoided (gunicorn worker import, Docker
|
||||
cold start when Spotify is unreachable).
|
||||
"""
|
||||
_default = METADATA_SOURCE_PRIORITY[0]
|
||||
return _get_config_value("metadata.fallback_source", _default) or _default
|
||||
|
||||
|
||||
def get_primary_source(spotify_client_factory: Optional[MetadataClientFactory] = None) -> str:
|
||||
"""Return configured primary metadata source."""
|
||||
_default = METADATA_SOURCE_PRIORITY[0]
|
||||
source = _get_config_value("metadata.fallback_source", _default) or _default
|
||||
source = get_configured_primary_source()
|
||||
|
||||
if source == "spotify":
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ from core.metadata.registry import (
|
|||
get_discogs_client,
|
||||
get_hydrabase_client,
|
||||
get_itunes_client,
|
||||
get_configured_primary_source,
|
||||
get_primary_client,
|
||||
get_primary_source,
|
||||
get_primary_source_label,
|
||||
|
|
@ -117,6 +118,7 @@ __all__ = [
|
|||
"get_metadata_service",
|
||||
"get_musicmap_similar_artists",
|
||||
"get_primary_client",
|
||||
"get_configured_primary_source",
|
||||
"get_primary_source",
|
||||
"get_primary_source_label",
|
||||
"get_spotify_client_for_profile",
|
||||
|
|
|
|||
|
|
@ -857,7 +857,8 @@ class SpotifyClient:
|
|||
# Use a dedicated probe client (retries=0) so a 429 here propagates
|
||||
# immediately and we can detect long Retry-After bans.
|
||||
try:
|
||||
probe = spotipy.Spotify(auth_manager=self.sp.auth_manager, retries=0)
|
||||
probe = spotipy.Spotify(
|
||||
auth_manager=self.sp.auth_manager, retries=0, requests_timeout=15)
|
||||
probe.current_user()
|
||||
result = True
|
||||
except Exception as e:
|
||||
|
|
|
|||
27
tests/metadata/test_configured_primary_source.py
Normal file
27
tests/metadata/test_configured_primary_source.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
"""Tests for boot-safe configured primary source lookup."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from core.metadata import registry
|
||||
|
||||
|
||||
def test_get_configured_primary_source_reads_config_without_auth_probe(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
registry,
|
||||
"_get_config_value",
|
||||
lambda key, default=None: "spotify" if key == "metadata.fallback_source" else default,
|
||||
)
|
||||
|
||||
with patch.object(registry, "get_spotify_client") as get_client:
|
||||
assert registry.get_configured_primary_source() == "spotify"
|
||||
get_client.assert_not_called()
|
||||
|
||||
|
||||
def test_get_primary_source_still_downgrades_unauthenticated_spotify(monkeypatch):
|
||||
monkeypatch.setattr(registry, "get_configured_primary_source", lambda: "spotify")
|
||||
|
||||
spotify = MagicMock()
|
||||
spotify.is_spotify_authenticated.return_value = False
|
||||
monkeypatch.setattr(registry, "get_spotify_client", lambda **_: spotify)
|
||||
|
||||
assert registry.get_primary_source() == registry.METADATA_SOURCE_PRIORITY[0]
|
||||
|
|
@ -36242,11 +36242,13 @@ except Exception as e:
|
|||
# they explicitly want background Spotify enrichment.
|
||||
spotify_enrichment_worker = None
|
||||
try:
|
||||
from core.metadata_service import get_primary_source as _get_primary_source
|
||||
from core.metadata_service import get_configured_primary_source
|
||||
from database.music_database import MusicDatabase
|
||||
spotify_enrichment_db = MusicDatabase()
|
||||
spotify_enrichment_worker = SpotifyWorker(database=spotify_enrichment_db)
|
||||
_primary = _get_primary_source()
|
||||
# Use configured source only — get_primary_source() probes Spotify auth and can
|
||||
# block gunicorn worker boot indefinitely when the API is unreachable.
|
||||
_primary = get_configured_primary_source()
|
||||
_user_paused = config_manager.get('spotify_enrichment_paused', False)
|
||||
if _user_paused or _primary != 'spotify':
|
||||
spotify_enrichment_worker.paused = True # Set BEFORE start() to prevent race condition
|
||||
|
|
|
|||
Loading…
Reference in a new issue