fix(zombies): "running" fehlte - ein abgestuerzter RIP wurde NIE gefunden
Ampel / ampel (push) Successful in 30s
Ampel / ampel (push) Successful in 30s
Der Commander: "Ausserdem ist gerade mitten im Rip das Laufwerk ausgegangen...
glaube ich zumindest." Gemessen war es etwas anderes, und der Fund ist groesser
als der Vorfall.
WAS MESSBAR WAR:
Job 2182d525 status=running progress=12 (Rip gestartet 14:00:24)
makemkvcon laeuft NICHT (per /proc geprueft, PID gegengeprueft)
Rohdatei 5.167.382.528 Bytes, waechst in 10 s nicht
Laufwerk Status 4 (Disc drin), /dev/sr0 + /dev/sg1 da
Worker-Start 14:07:02 <- mein `docker compose up -d --build`
Das Laufwerk ist also NICHT ausgegangen. Der Rip wurde von MEINEM Deploy
getoetet: `up -d --build` baut den worker-Container neu, und der laufende Rip
stirbt mit ihm. Genau davor warnt der SAVEPOINT seit v3.18 - die Warnung half
nichts, weil sie niemand liest und nichts sie prueft.
DER EIGENTLICHE FUND: Die Zombie-Erkennung lief um 14:09:04 und meldete
`{'geprueft': 0, 'aufgeraeumt': []}` - obwohl der tote Job direkt vor ihr lag.
Ursache:
ARBEITS_STATI = ("ripping", "transcoding", "canceling") # zombies.py
db.update_job(job_id, status="running", ...) # tasks.py - der Rip
Der Rip setzt "running", gesucht wurde "ripping". Dieser Wert steht
ausschliesslich in Celerys Task-META und NIE in einer Job-Zeile (nachgeprueft:
kein einziger Schreiber im ganzen Baum). Die Zombie-Erkennung aus v3.14 wurde
gebaut, um genau einen abgestuerzten Rip zu finden - und hat ihn nie gesehen.
Besonders tueckisch: `geprueft: 0` sah bei jedem Worker-Start wie "nachgesehen,
alles gesund" aus, waehrend sie nach einem Status suchte, den es nicht gibt.
Deshalb blieb der Job auf "processing 12 %" stehen - mit einer Restzeit-Schaetzung
von 1 h 30 min obendrauf, die es fuer einen toten Prozess nicht geben duerfte.
GEBAUT:
* "running" in ARBEITS_STATI.
* Ein Test, der das Auseinanderlaufen MECHANISCH verhindert: Er liest tasks.py,
sammelt jeden Status, den der Worker per db.update_job in eine Job-Zeile
schreibt, und verlangt, dass jeder davon entweder ein Arbeitsstatus oder ein
Endzustand ist. Ein Kommentar haette das nicht verhindert.
* GET /health/arbeit + eine Sperre in deploy.sh: Laeuft ein Job, bricht der
Deploy ab (uebersteuerbar mit RIPPY_TROTZDEM=1 - dann ist es eine
Entscheidung und kein Versehen). Ein Satz Code gegen eine verlorene Stunde.
* Server-Status zeigt jetzt die eingehaengten FREIGABEN mit freiem Platz, nicht
nur die Container-Platte (Commander-Wunsch). Genau dort liegen die Rohdaten,
und bei externem Encoden muessen sie dort liegen - wer wissen wollte, ob noch
Platz fuer eine Disc ist, sah die falsche Zahl.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -188,3 +188,54 @@ def test_fehler_reisst_den_worker_start_nicht_mit():
|
||||
db = KaputteDb([])
|
||||
bericht = zombies.raeume_zombies_auf(FakeCelery(FakeInspektor()), db)
|
||||
assert "Postgres weg" in bericht["uebersprungen"]
|
||||
|
||||
|
||||
# --- Die Luecke, die die Erkennung nutzlos machte (Befund 26.07.2026) --------
|
||||
|
||||
|
||||
def test_running_gilt_als_arbeitsstatus():
|
||||
"""DER Fehler: Die Erkennung suchte "ripping", der Rip setzt aber "running".
|
||||
Ergebnis: Ein abgestuerzter RIP wurde NIE gefunden - und die Meldung
|
||||
`{'geprueft': 0}` sah bei jedem Worker-Start wie Gesundheit aus."""
|
||||
assert "running" in zombies.ARBEITS_STATI
|
||||
assert "transcoding" in zombies.ARBEITS_STATI
|
||||
assert "canceling" in zombies.ARBEITS_STATI
|
||||
|
||||
|
||||
def test_abgestuerzter_rip_wird_gefunden():
|
||||
"""Der konkrete Fall vom 26.07.2026: Job 2182d525 stand auf running/12 %,
|
||||
kein makemkvcon lief, die Rohdatei wuchs nicht mehr - und die Erkennung
|
||||
pruefte null Jobs."""
|
||||
offene = [{"id": "2182d525", "status": "running", "title": "Akira"}]
|
||||
assert zombies.finde_zombies(offene, set()) == offene
|
||||
|
||||
|
||||
def test_arbeitsstati_deckt_ab_was_der_worker_wirklich_schreibt():
|
||||
"""Mechanische Sperre gegen genau dieses Auseinanderlaufen.
|
||||
|
||||
Liest tasks.py und sammelt jeden Status, den der Worker per db.update_job in
|
||||
eine Job-ZEILE schreibt. Jeder davon muss entweder ein Arbeitsstatus sein
|
||||
oder ein Endzustand - sonst gibt es wieder einen Zustand, den niemand
|
||||
aufraeumt. Ein Kommentar haette das nicht verhindert; dieser Test schon.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
|
||||
pfad = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tasks.py")
|
||||
quelle = open(pfad, encoding="utf-8").read()
|
||||
|
||||
# Nur die Aufrufe, die wirklich die Job-Zeile aendern.
|
||||
geschrieben = set()
|
||||
for aufruf in re.finditer(r"db\.update_job\((?:[^()]|\([^()]*\))*\)", quelle):
|
||||
for treffer in re.finditer(r'status\s*=\s*"([a-z]+)"', aufruf.group(0)):
|
||||
geschrieben.add(treffer.group(1))
|
||||
|
||||
endzustaende = {"completed", "failed"}
|
||||
unbeaufsichtigt = geschrieben - set(zombies.ARBEITS_STATI) - endzustaende
|
||||
assert not unbeaufsichtigt, (
|
||||
f"Diese Job-Status schreibt der Worker, aber niemand raeumt sie auf: "
|
||||
f"{sorted(unbeaufsichtigt)}. Entweder in ARBEITS_STATI aufnehmen oder "
|
||||
f"als Endzustand behandeln."
|
||||
)
|
||||
# Gegenprobe, dass der Test wirklich etwas gesehen hat
|
||||
assert "running" in geschrieben
|
||||
|
||||
@@ -28,7 +28,22 @@ Ampel sie ohne Infrastruktur prüfen kann.
|
||||
"""
|
||||
|
||||
# Zustände, die behaupten: hier arbeitet gerade jemand.
|
||||
ARBEITS_STATI = ("ripping", "transcoding", "canceling")
|
||||
#
|
||||
# ⚠️ „running" FEHLTE bis zum 26.07.2026 — und das war der ganze Witz: Die
|
||||
# Zombie-Erkennung aus v3.14 wurde gebaut, um genau einen abgestürzten RIP zu
|
||||
# finden, und hat ihn nie gesehen. Der Rip setzt `status="running"`
|
||||
# (tasks.py, db.update_job), gesucht wurde aber „ripping". Dieser Wert steht
|
||||
# ausschließlich in Celerys Task-Meta und NIE in einer Job-Zeile.
|
||||
#
|
||||
# Die Folge war besonders tückisch: Bei jedem Worker-Start meldete die Erkennung
|
||||
# `{'geprueft': 0, ...}` — und das sah wie „nachgesehen, alles gesund" aus,
|
||||
# während sie in Wahrheit nach einem Status suchte, den es nicht gibt. Gefunden
|
||||
# am 26.07.2026, als ein Rip mitten im Lauf abbrach und der Job danach dauerhaft
|
||||
# auf „processing 12 %" stand, mit einer Restzeit-Schätzung obendrauf.
|
||||
#
|
||||
# „ripping" bleibt bewusst drin: Es schadet nicht und deckt eine etwaige
|
||||
# Bestandsinstallation ab, in der es doch gesetzt wurde.
|
||||
ARBEITS_STATI = ("running", "ripping", "transcoding", "canceling")
|
||||
|
||||
# Wartezeit nach dem Worker-Start, bevor geurteilt wird. Deckt die
|
||||
# Wiederzustellung unbestätigter Aufgaben durch Celery ab.
|
||||
|
||||
Reference in New Issue
Block a user