docs(savepoint): v3.19 - sieben Meldungen, drei echte Fehler, eine Kette
Ampel / ampel (push) Successful in 30s

Der Savepoint haelt fest, was gemessen wurde und was nicht. Die wichtigste Lehre
steht auch in AGENTS.md: Bei "zu langsam" die DAUER je Schritt messbar machen
statt die plausibelste Ursache zu beheben. Meine erste Erklaerung fuer die 150 s
war falsch und machte es sogar langsamer (202 s); erst Zeitstempel im Log zeigten
die Stelle - ein os.makedirs, das drei Minuten im Kernel hing. Danach 8 s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-26 15:25:46 +02:00
parent 848deb1c24
commit b4a0dd551a
2 changed files with 260 additions and 1 deletions
+249 -1
View File
@@ -1,6 +1,254 @@
# SAVEPOINT — Rippy
## Aktueller Stand: v3.18Auswurf, externer Worker, und DIE Mount-Ursache (26.07.2026, Abend)
## Aktueller Stand: v3.19Sprachwahl, Verwaltungsfenster, Schlüssel-Automatik (26.07.2026, spät)
> **Deployt und live gegengeprüft.** Sieben Commander-Meldungen, alle gemessen
> statt vermutet. Drei davon waren echte Fehler, die niemand gesehen hatte, und
> einer davon hat sich als Kette aus drei Ursachen entpuppt.
### ZUSTAND, gemessen
| | |
|---|---|
| Repo + VM | `848deb1`, Ampel **grün**, deployt |
| Tests | **269** grün (Sitzungsbeginn: 130) |
| Freigabe nach Deploy | heilt sich in **8 Sekunden** selbst (vorher 150202 s) |
| Job `95afdc89` | `can_retry = true`, 74,1 GB erreichbar |
| Sprach-Scan an der Akira-BD | Ton `deu 4×, jpn 2×, und 4×` · Untertitel `deu 4×, und 8×` |
### 1. AUSWURF: das ioctl meldete Erfolg und tat nichts
Am laufenden System an der eingelegten Disc nachgestellt:
```
wirf_disc_aus("/dev/sr0") → True
Laufwerksstatus danach → 4 (Disc drin)
```
MakeMKV **verriegelt die Laufwerkstür** während des Rips (`CDROM_LOCKDOOR 1`) und
entriegelt sie nicht wieder. Ein verriegeltes Laufwerk quittiert `CDROMEJECT`
trotzdem mit Erfolg. Gegenprobe an derselben Disc: mit `CDROM_LOCKDOOR 0` davor
→ Status 2 (Schublade offen). Genau das macht das Werkzeug `eject` immer.
Wichtiger als das Entriegeln: Das Ergebnis wird jetzt **geprüft statt geglaubt**.
Deshalb rutschte der Fehler in v3.14 durch — dort wurde richtig festgestellt,
dass die Einstellung von niemandem gelesen wurde; danach WURDE sie gelesen,
ausgeworfen wurde weiterhin nicht, und im Log stand „Disc ausgeworfen".
**Live bewiesen** (API-Weg = der Knopf im UI): 0,67 s von „Disc drin" zu
„Schublade offen". Der automatische Weg nach einem Rip ist derselbe Code und
durch Tests gedeckt, aber nicht nochmal live gemessen — die Schublade lässt sich
per Software nicht wieder schließen (`CDROMCLOSETRAY` bleibt wirkungslos,
viermal versucht; das BU40N will von Hand zugeschoben werden).
**Zweite Disc parallel** war nur daran gescheitert: `has_active_job` blockiert
ausschließlich bei `pending`/`running`, ein Job in `transcoding` gibt das Gerät
längst frei, und der Docker-Worker läuft ohne `--concurrency`, also mit 4 Slots.
### 2. SPRACHWAHL VOR DEM RIP
Die Auskunft lag längst vor und wurde weggeworfen: Derselbe Titel-Scan, der die
Titel-Tabelle füllt, liefert in derselben `makemkvcon`-Ausgabe die Streams mit.
Format an der Akira-Blu-ray gemessen:
```
SINFO:<titel>,<stream>,<attribut>,<code>,"<wert>"
1 = Typ ("Audio"/"Subtitles") 3 = Sprachcode 4 = Sprachname
6 = Codec 14 = Kanäle 30 = Beschreibung
```
⚠️ **Die Falle:** Die Sprache steht in **3/4, NICHT in 28/29**. Die tragen auf
JEDEM Stream „eng"/„English" — auch auf einer deutschen Tonspur und auf dem
Videostream; das ist MakeMKVs eigene Anzeigesprache. Wer 28 nimmt, hält jede Disc
für englisch. Ein Test hält das fest.
**Angewendet wird bei der KOMPRESSION, nicht beim Rippen** — drei Gründe: Der Rip
bleibt vollständig und verlustfrei (Muss-Feature laut KONZEPT); HandBrake hat
dafür dokumentierte Schalter (`--audio-lang-list` / `--subtitle-lang-list`, die
genau die ISO-639-2-Codes nehmen, die MakeMKV liefert — beides gegengeprüft); und
wer später andere Sprachen will, komprimiert neu statt die Disc wieder
einzulegen. Genau das sagt der Dialog auch, sonst glaubt man, es werde
unvollständig gerippt.
Nichts angeklickt heißt „alles behalten". Auch die Einstellung ist bewusst LEER
vorbelegt: Ein stilles „deu" würde bei einem japanischen Original die
Originaltonspur wegwerfen, ohne dass jemand gefragt hat. Wunschsprachen für die
Automatik stehen unter Einstellungen → Verarbeitung.
### 3. DER EXTERNE WORKER: Deinstaller, Verwaltungsfenster, Slots
**Der Deinstaller lief nicht mehr** — reproduziert mit echtem PowerShell:
```
Der Typ [System.Windows.Forms.MessageBox] wurde nicht gefunden.
```
`Add-Type -AssemblyName System.Windows.Forms` stand **eine Zeile zu spät**. Der
Deinstaller starb in seiner ersten Arbeitszeile, jedes Mal. Zwei weitere Mängel
gleich mit: ASCII-Kodierung trotz Umlauten, und `Remove-Item -Recurse -Force
$PSScriptRoot` löscht den Ordner, in dem das laufende Skript liegt (klappt auf
Windows nicht zuverlässig — venv-DLLs sind geladen). Jetzt räumt ein losgelöstes
`cmd` nach, sobald PowerShell weg ist. Der erzeugte Deinstaller ist
gegengeprüft: parst, BOM da, Umlaute intakt.
**Verwaltungsfenster** (Doppelklick aufs Tray): Status, Aufgaben, Log, plus
Knöpfe für Rippy, Log-in-Rippy und Deinstallieren — das geht damit auch aus dem
Tray-Menü. Eigener PROZESS statt Fenster im Tray, weil pystray und tkinter beide
den Haupt-Thread wollen; tkinter statt WinForms, weil es bei jeder
Windows-Python-Installation dabei ist. Headless gerendert und angesehen. Alles
Fachliche kommt von Rippy (`/capabilities`, `/jobs`), damit dort keine zweite,
abweichende Wahrheit steht.
**Slots einstellbar.** Vorher fest `--pool=solo` = genau EIN Auftrag. Der
Installer fragt die Zahl jetzt, Vorbelegung ab 12 Kernen zwei, sonst einer:
HandBrake nutzt schon alle Kerne, aber x265 skaliert nicht linear. Auf Windows
gibt es keinen prefork-Pool (kein `fork`) — deshalb `--pool=threads`, was passt,
weil die Arbeit ein Kind-Prozess ist und der Thread nur wartet. Auf dem
Commander-PC erkennt der Installer 16 Kerne und schlägt 2 vor.
**Log geht nach Rippy** (`logbruecke.py`, Quelle `w:<name>`), die Logs-Seite hat
Knöpfe je Quelle. Durchgelassen wird wenig und mit Grund: Die Job-Meldungen
stehen längst in Rippy; gefehlt hat, was DANEBEN passiert (hochgefahren oder
nicht, Verbindung, Abstürze). Dazu eine Drossel (30 Zeilen/Minute), die MELDET,
wieviel sie verschluckt hat. Die lokale Datei bleibt — sie ist genau dann die
einzige Auskunft, wenn Rippy nicht erreichbar ist.
### 4. SCHLÜSSEL-AUTOMATIK für 4K-UHD (auf Commander-Entscheid)
`makemkvcon` unter LINUX ruft Disc-Schlüssel nie ab, die WINDOWS-Version schon.
Bisher Handarbeit: Laufwerk an den PC, Disc öffnen, `_private_data.tar` suchen,
im UI hochladen. Läuft jetzt von selbst — **zweischichtig, mit Absicht**:
1. **Der Wächter (verlässlich):** sieht `_private_data.tar` nach und lädt sie zu
Rippy hoch, sobald sie sich geändert hat. Keine Laufwerkserkennung, nichts
geraten. Deckt auch ab, dass man die Disc einfach in MakeMKV öffnet.
2. **Das Anstoßen (nach bestem Wissen):** liegt eine Disc im Laufwerk, wird
`makemkvcon info` darauf losgelassen — dabei holt MakeMKV den Schlüssel.
Warum getrennt: Das Format der BELEGTEN `DRV:`-Zeile ließ sich nicht messen (der
Commander-PC hat kein optisches Laufwerk — alle 16 Plätze melden
`DRV:i,256,999,0,"","",""`, das ist gemessen). Geraten wird also nur in Schicht 2,
und wenn die Vermutung falsch ist, passiert dort einfach nichts. **Die teure
Annahme steckt nie im verlässlichen Teil.**
Gemessene Fundstellen: Datenverzeichnis ist `%USERPROFILE%\.MakeMKV` (NICHT
`%APPDATA%\MakeMKV`) — dort lag die echte Datei mit 6.420.480 Bytes. Programm:
`C:\Program Files (x86)\MakeMKV\makemkvcon64.exe`, v1.18.4. Die Automatik
schaltet sich ab, wenn MakeMKV fehlt: Auf einem reinen Encoding-PC gibt es nichts
zu holen.
### 5. DIE 150 SEKUNDEN — drei Ursachen in einer Kette
Der Commander: *„150 Sekunden für das Neu-Anbinden eines Mounts? Das ist verrückt
langsam."* Er hatte recht, und die erste Vermutung war falsch. Erst die
Zeitstempel im Log zeigten, wo die Zeit sitzt:
```
12:53:56 API gestartet
12:54:03 „antwortet nicht — wird neu verbunden" ← Erkennung: 7 s, gut
12:57:18 „eingehängt" ← Reparatur: 3 min 15 s
```
Die Reparatur hing in der **ersten Zeile** von `mounten()`:
```python
os.makedirs(ziel, exist_ok=True)
```
`exist_ok` prüft mit `os.path.isdir`, und ein `stat` auf einen toten CIFS-Mount
blockiert im Kernel bis zum SMB-Timeout. Ausgerechnet der Aufruf, der „lege den
Ordner an, falls er fehlt" bedeutet, hing drei Minuten — **bevor** irgendeine der
sorgfältig begrenzten Prüfungen dran war. Dritter Fund derselben Sorte an einem
Tag: os-Aufruf auf einen Netzpfad ohne Zeitgrenze.
Jetzt klärt `pfad_lage()` das mit einem abbrechbaren Kind-Prozess (`timeout 4
ls -d`, drei Antworten: da / weg / unklar). Dieselbe Falle steckte in
`reparieren()` (`os.path.ismount` als Vorbedingung) und `aushaengen()`
(`ismount` + `rmdir`). Dazu: Wache prüft erstmals nach 3 s statt 60 s, benutzt
zum Erkennen die schnelle Einzelprobe, und die **Dauer steht jetzt im Log**.
**Gemessen: 202 s → 8 s.**
### 6. DER WIDERSPRUCH AUF DEM DASHBOARD
Oben stand „Akira im Laufwerk erkannt", die Server-Status-Karte gleichzeitig
„Bereit — keine Disc in Arbeit / Disc einlegen". Zwei Aussagen, ein Blick. Die
Karte fragt jetzt `/devices` und sagt „Disc erkannt — wartet auf Rippen
starten'" samt Titel.
### 7. WEITERGABE GEPRÜFT — drei Lücken geschlossen
Nicht durch Lesen, sondern indem ich mich wie ein fremder Rechner verhalten habe:
frischer `git clone` von Gitea in ein leeres Verzeichnis, dann
`./install.sh --nur-pruefen`. **2,6 MB, alles grün**, Laufwerk samt richtigem
sg-Knoten über die SCSI-Adresse erkannt. Der Weg trägt.
Drei Lücken fielen auf:
1. **In Beispielbefehlen stand meine IP.** `install.ps1` und
`remote-transcode-worker.yml` nannten 192.168.178.162 — genau die Zeile, die
ein Fremder kopiert. Jetzt Platzhalter, beim Windows-Installer mit dem Hinweis,
wo die richtige IP steht (und dass es NICHT die des eigenen PCs ist).
2. **Der Assistent fragte den MakeMKV-Beta-Key nicht.** Er stand in der README,
in der `.env` und in den Einstellungen — nur nicht dort, wo man beim
Einrichten hinsieht. Ein Fremder installiert, legt eine Blu-ray ein und
bekommt später einen Fehlschlag, ohne Hinweis. **Wahrscheinlichste
Stolperstelle einer frischen Installation.** Jetzt fragt der Assistent ihn ab,
mit dem Unterschied im Klartext: DVDs gehen ohne, Blu-ray braucht ihn — und er
ist NICHT der Disc-Schlüssel einer 4K-Disc.
3. Eine Docstring nannte die NAS-IP als Beispiel → neutral.
Tests und Log-Beispiele behalten die echten Namen: Sie dokumentieren Messungen.
### NOCH OFFEN
**1. Der gemeinsame Weitergabe-TEST steht aus** — geprüft ist die Vorbereitung,
nicht der Durchlauf auf einem fremden Rechner. Ungetestet bleibt dabei weiterhin
`install.sh` **als root** (Verzeichnisse anlegen, `mount --make-rshared`,
systemd-Unit) — auf dieser VM sind alle root-Schritte No-Ops.
**2. Der api-Container HÄLT die NAS-Verbindung** (Namespace-Befund aus v3.18).
Startet er mitten in einem Rip neu, verliert auch der Worker sein Ziel. Bis auf
Weiteres: nicht deployen, während ein Rip läuft. Sauber wäre ein Mount auf dem
HOST — eigener Umbau, widerspräche „Speicherziele über das UI".
**3. Die Schublade der VM steht offen** und braucht einen Handgriff.
**4. Der externe Worker läuft weiter mit altem Code.** Er braucht einen Lauf des
neuen Installers für: Pfad-Mapping, Preset-Meldung, Log-Brücke, Tray-Anzeige,
Slots, Verwaltungsfenster, Schlüssel-Automatik. Danach sind auch die
Live-Gegenproben möglich, die jetzt fehlen — mehrere Encodes gleichzeitig
(bisher nur die richtige Celery-Option, kein gemessener Durchsatz), die
Schlüssel-Automatik an einer echten UHD-Disc, und die belegte `DRV:`-Zeile.
**5. Sprachauswahl live an einem echten Encode** — der Scan ist gegengeprüft
(Sprachen kommen korrekt an), die Anwendung in HandBrake noch nicht.
**6. Unverändert offen:** Live-Gegenprobe der Metadaten-Kette (MyAnimeList war
die ganze Sitzung weg, HTTP 504); ISO-Sicherung und Mehr-Laufwerk-Betrieb;
Klartext-Geheimnisse in der settings-Tabelle; VM-CPU-Typ auf `qemu64`; kein
TypeScript-Typcheck.
### FALLEN DIESER RUNDE
- **Dreimal derselbe Fehlertyp an einem Tag:** ein `os`-Aufruf auf einen Netzpfad
ohne Zeitgrenze (`isdir` in der Rohdaten-Suche, `makedirs` im Mounten,
`ismount`/`rmdir` im Reparieren/Aushängen). Wer im Container einen Pfad unter
`/app/media` anfasst, nimmt einen abbrechbaren Kind-Prozess.
- **Meine erste Erklärung für die 150 s war falsch** (Wettlauf mit `umount -l`).
Die Änderung machte es sogar langsamer (202 s). Erst Zeitstempel im Log haben
die Stelle gezeigt. Lehre: bei „langsam" nicht die plausibelste Ursache
beheben, sondern die Dauer je Schritt messbar machen.
- **Ein Stub muss zur neuen Aufrufzahl passen.** Zwei Tests brachen, als eine
Prüfung zu zwei wurde bzw. ein `timeout ls` dazukam. Besser die HÖHERE Funktion
stubben als Aufrufe zählen.
- **`Add-Type` gehört VOR die erste Benutzung des Typs.** Klingt banal; hat den
Deinstaller monatelang komplett lahmgelegt, ohne dass es auffiel.
---
## Vorheriger Stand: v3.18 — Auswurf, externer Worker, und DIE Mount-Ursache (26.07.2026, Abend)
> **Deployt und live gegengeprüft.** Zwei Commander-Meldungen: Das Laufwerk geht
> nach dem Rip nicht auf, und der externe Worker „ist ein bisschen dünn". Beide