From 6aa4032134b391c92536c14af633bb03c3af4992 Mon Sep 17 00:00:00 2001 From: nessi Date: Fri, 21 Aug 2026 21:02:27 +0200 Subject: [PATCH] feat: add non-blocking RAID status refresh with dedicated lock and remove offline navigation fallback from service worker Add raid_refresh_lock to prevent concurrent RAID refresh operations. Extract refresh_raid_status() function with non-blocking lock acquisition using acquire(blocking=False). Update datastore_snapshot() to spawn background thread for RAID refresh when cache is stale but return immediately with cached data. Remove offline navigation fallback from service worker to prevent masking backend errors. Bump service worker cache version to v7 --- app.py | 35 +++++++++++++++++++++++++++-------- static/sw.js | 9 +++++---- templates/index.html | 4 ++-- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/app.py b/app.py index f653878..49a08e0 100644 --- a/app.py +++ b/app.py @@ -83,6 +83,7 @@ service_status_lock = threading.Lock() service_check_executor = ThreadPoolExecutor(max_workers=16) datastore_cache_lock = threading.Lock() datastore_refresh_lock = threading.Lock() +raid_refresh_lock = threading.Lock() datastore_cache = {"stored_at": 0.0, "snapshot": None, "raid": None, "raid_at": 0.0} raid_log_state = None hardware_log_once = set() @@ -549,6 +550,25 @@ def refresh_datastore_storage(): finally: datastore_refresh_lock.release() +def refresh_raid_status(): + if not raid_refresh_lock.acquire(blocking=False): + return + try: + current = raid_status() + with datastore_cache_lock: + if current.get("status") == "failed" and current.get("error") and datastore_cache.get("raid"): + cached = dict(datastore_cache["raid"]) + cached["stale"] = True + cached["warning"] = "Vorübergehend keine aktuelle RAID-Antwort." + datastore_cache["raid"] = cached + else: + datastore_cache["raid"] = current + datastore_cache["raid_at"] = time.time() + except Exception as exc: + log.error("RAID-Refresh fehlgeschlagen: %s", error_summary(exc)) + finally: + raid_refresh_lock.release() + def datastore_snapshot(): now = time.time() with datastore_cache_lock: @@ -560,15 +580,14 @@ def datastore_snapshot(): threading.Thread(target=refresh_datastore_storage, name="datastore-refresh", daemon=True).start() with datastore_cache_lock: storage = datastore_cache["snapshot"] - current_raid = raid_status() with datastore_cache_lock: - if current_raid.get("status") == "failed" and current_raid.get("error") and datastore_cache.get("raid") and now - datastore_cache.get("raid_at", 0) < 60: - current_raid = dict(datastore_cache["raid"]) - current_raid["stale"] = True - current_raid["warning"] = "Vorübergehend keine aktuelle RAID-Antwort; letzter gültiger Stand wird angezeigt." - elif current_raid.get("status") != "failed": - datastore_cache["raid"] = dict(current_raid) - datastore_cache["raid_at"] = now + current_raid = datastore_cache.get("raid") + raid_age = now - datastore_cache.get("raid_at", 0) + if current_raid is None: + threading.Thread(target=refresh_raid_status, name="raid-refresh", daemon=True).start() + current_raid = {"device": RAID_DEVICE, "status": "unknown", "label": "WIRD GELADEN", "error": "RAID-Status wird geladen"} + elif raid_age >= 5: + threading.Thread(target=refresh_raid_status, name="raid-refresh", daemon=True).start() return {"storage": storage, "raid": current_raid, "io_pressure": io_pressure(), "cache_seconds": STORAGE_CACHE_SECONDS} def cpu_usage_percent(): diff --git a/static/sw.js b/static/sw.js index 6a62cb3..e9d2e00 100644 --- a/static/sw.js +++ b/static/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'media-max-shell-v6'; +const CACHE_NAME = 'media-max-shell-v7'; const SHELL = ['/static/media-mark.svg', '/static/media-mark-192.png', '/static/media-mark-512.png', '/static/manifest.json']; self.addEventListener('install', event => { @@ -16,10 +16,11 @@ self.addEventListener('fetch', event => { if (request.method !== 'GET') return; const url = new URL(request.url); if (url.pathname.startsWith('/api/')) return; + // Navigation requests must stay real network requests. Returning an + // offline document here can mask backend errors and make the app appear + // disconnected after a scan/reload. + if (request.mode === 'navigate') return; event.respondWith(fetch(request).catch(() => { - if (request.mode === 'navigate') { - return new Response('Media Max

Media Max

Die Verbindung ist momentan unterbrochen. Bitte erneut laden.

', {headers: {'Content-Type': 'text/html; charset=utf-8'}}); - } return caches.match(request).then(response => response || new Response('', {status: 503})); })); }); diff --git a/templates/index.html b/templates/index.html index 3c841ae..39331a3 100644 --- a/templates/index.html +++ b/templates/index.html @@ -29,7 +29,7 @@ document.addEventListener('DOMContentLoaded',()=>{const grid=document.querySelec @@ -50,7 +50,7 @@ if ('serviceWorker' in navigator) { -Media Max +Media Max