diff --git a/backend/services/preferences_service.py b/backend/services/preferences_service.py index d424fdb..48db40c 100644 --- a/backend/services/preferences_service.py +++ b/backend/services/preferences_service.py @@ -40,7 +40,7 @@ class PreferencesService: self._settings = settings self._config_path = settings.config_file_path self._config_cache: Optional[dict] = None - self._cache_lock = threading.Lock() + self._cache_lock = threading.RLock() self._migrate_musicbrainz_settings() self._ensure_instance_id() diff --git a/backend/static_server.py b/backend/static_server.py index 630c481..41cf5c4 100644 --- a/backend/static_server.py +++ b/backend/static_server.py @@ -4,6 +4,9 @@ from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles +_NO_CACHE_HEADERS = {"Cache-Control": "no-cache"} + + def mount_frontend(app: FastAPI): backend_static = Path(__file__).parent / "static" frontend_root = Path(__file__).resolve().parents[1] / "frontend" @@ -83,7 +86,7 @@ def mount_frontend(app: FastAPI): @app.get("/") async def serve_root(): if index_html.exists(): - return FileResponse(index_html) + return FileResponse(index_html, headers=_NO_CACHE_HEADERS) raise HTTPException(status_code=404, detail="Frontend not built yet") @app.get("/{full_path:path}") @@ -91,5 +94,5 @@ def mount_frontend(app: FastAPI): if full_path.startswith("api"): raise HTTPException(status_code=404, detail="API route not found") if index_html.exists(): - return FileResponse(index_html) + return FileResponse(index_html, headers=_NO_CACHE_HEADERS) raise HTTPException(status_code=404, detail="Frontend not built yet") diff --git a/backend/tests/routes/test_plex_auth.py b/backend/tests/routes/test_plex_auth.py index 02e211c..775c881 100644 --- a/backend/tests/routes/test_plex_auth.py +++ b/backend/tests/routes/test_plex_auth.py @@ -77,6 +77,43 @@ class TestCreatePlexPin: assert resp.status_code == 500 +class TestGetOrCreateSettingNoDeadlock: + def test_get_or_create_setting_does_not_deadlock(self, tmp_path): + import threading + + from core.config import Settings + from services.preferences_service import PreferencesService + + config_path = tmp_path / "config.json" + settings = Settings( + root_app_dir=tmp_path, + config_file_path=config_path, + cache_dir=tmp_path / "cache", + library_db_path=tmp_path / "cache" / "library.db", + queue_db_path=tmp_path / "cache" / "queue.db", + ) + + result = None + exc = None + + def run(): + nonlocal result, exc + try: + prefs = PreferencesService(settings) + result = prefs.get_or_create_setting("plex_client_id", lambda: "test-client-id") + result = (result, prefs.get_or_create_setting("plex_client_id", lambda: "other")) + except Exception as e: + exc = e + + t = threading.Thread(target=run) + t.start() + t.join(timeout=5) + assert not t.is_alive(), "Deadlock detected: PreferencesService hung for 5s" + assert exc is None + assert result[0] == "test-client-id" + assert result[1] == "test-client-id" + + class TestPollPlexPin: def test_poll_pending(self, auth_client): resp = auth_client.get("/plex/auth/poll?pin_id=12345") diff --git a/frontend/src/lib/queries/VersionQuery.svelte.ts b/frontend/src/lib/queries/VersionQuery.svelte.ts index 032c4fb..705cd9d 100644 --- a/frontend/src/lib/queries/VersionQuery.svelte.ts +++ b/frontend/src/lib/queries/VersionQuery.svelte.ts @@ -30,7 +30,8 @@ export const getVersionQuery = () => staleTime: CACHE_TTL.VERSION_INFO, queryKey: VersionQueryKeyFactory.info(), queryFn: ({ signal }) => api.global.get(API.version.info(), { signal }), - refetchOnWindowFocus: false + refetchOnWindowFocus: false, + refetchOnMount: 'always' })); export const getUpdateCheckQuery = () => diff --git a/frontend/src/routes/+error.svelte b/frontend/src/routes/+error.svelte new file mode 100644 index 0000000..9f107f6 --- /dev/null +++ b/frontend/src/routes/+error.svelte @@ -0,0 +1,28 @@ + + + + Error - MusicSeerr + + +
+
+ +

Something went wrong

+

+ {page.error?.message ?? 'An unexpected error occurred'} +

+
+ + + Home + + +
+
+