diff --git a/docker/api/main.py b/docker/api/main.py index ed77a62..789b610 100644 --- a/docker/api/main.py +++ b/docker/api/main.py @@ -304,6 +304,34 @@ async def health_check(): return {"status": "ok", "service": "api"} +@app.get("/health/vorraete") +async def health_vorraete(): + """Laufen die Hintergrund-Schleifen wirklich? (Diagnose, kein UI-Endpunkt) + + Zwei Endpunkte hängen an einem Vorrat, den eine Hintergrund-Schleife füllt: + /capabilities am Celery-Ping, /jobs an der Rohdaten-Suche. Bleibt so ein + Vorrat leer, ist am Endpunkt selbst NICHTS zu sehen — er antwortet nur + dauerhaft „nichts gefunden". Genau daran ging am 26.07.2026 eine Stunde + verloren. `alter_sekunden` sagt, wann die Schleife das letzte Mal + durchgelaufen ist; wächst der Wert über das Intervall, läuft sie nicht mehr. + """ + jetzt = time.monotonic() + return { + "ping": { + "knoten": _PING["knoten"], + "alter_sekunden": None if _PING["stand"] < 0 else round(jetzt - _PING["stand"], 1), + "intervall": PING_INTERVALL_SEKUNDEN, + }, + "rohdaten": { + "jobs_im_vorrat": len(_ROHDATEN["treffer"]), + "mit_treffer": sum(1 for v in _ROHDATEN["treffer"].values() if v), + "alter_sekunden": None if _ROHDATEN["stand"] is None + else round(jetzt - _ROHDATEN["stand"], 1), + "intervall": ROHDATEN_INTERVALL_SEKUNDEN, + }, + } + + @app.get("/") async def root(): return { @@ -329,17 +357,22 @@ def _rohdaten_suchen(job_id: str, work_dir: str) -> list: # /capabilities (v3.15): Der Endpunkt wird alle 4 Sekunden vom Dashboard # abgefragt und darf NIE am Dateisystem hängen. Ein schlafendes NAS hätte das # Dashboard sonst für 10 Sekunden je Aufruf eingefroren. -_ROHDATEN = {"treffer": {}} +_ROHDATEN = {"treffer": {}, "stand": None} ROHDATEN_INTERVALL_SEKUNDEN = 30 async def _rohdaten_schleife(): - """Hält den Rohdaten-Vorrat frisch. Darf nie sterben.""" + """Hält den Rohdaten-Vorrat frisch. Darf nie sterben. + + Der Fehler wird GEMELDET, nicht verschluckt: Ein `except Exception: pass` + stand hier zuerst, und als der Vorrat leer blieb, war nicht zu sehen, warum + (Sitzung 26.07.2026 — eine Stunde Rätselraten für eine Zeile Log). + """ while True: try: await asyncio.to_thread(_rohdaten_vorrat_auffrischen) - except Exception: # DB/NAS weg → beim nächsten Durchlauf erneut - pass + except Exception as e: # DB/NAS weg → beim nächsten Durchlauf erneut + print(f"Rohdaten-Vorrat fehlgeschlagen: {type(e).__name__}: {e}") await asyncio.sleep(ROHDATEN_INTERVALL_SEKUNDEN) @@ -352,6 +385,7 @@ def _rohdaten_vorrat_auffrischen() -> None: continue treffer[zeile["id"]] = _rohdaten_suchen(zeile["id"], work_dir) _ROHDATEN["treffer"] = treffer + _ROHDATEN["stand"] = time.monotonic() def _kann_neu_komprimieren(job: dict, work_dir: str) -> bool: