Hallo zusammen,
erst mal vielen Dank für den MediaHub! Da ich 3 Espuinos mit identischen Medien haben, habe ich schon lange auf sowas gehofft. Da ich etwas ähnliches noch nie gemacht habe, habe ich mich von Claude an der Hand nehmen lassen, was erstaunlich gut funktioniert hat.
Dabei bin ich auf das Problem gestoßen, dass der Docker immer wieder unerwartet beendet wurde und neu gestartet ist.
Mit Claude konnte ich einen Workaround entwickeln. Aber ich wollte den Fehler zumindest melden, damit jemand mit mehr Ahnung vom Thema vielleicht mal sehen kann ob da was dran ist.
Ich betreibe den Docker auf einem Synology NAS (Container Manager, docker compose, Dockerfile unverändert, 2 gunicorn-Worker).
Problem
Der Container bricht beim Start ab und startet dann neu:
File „/app/app.py“, line 44, in
store = store_lib.Store(DATA_DIR)
File „/app/store.py“, line 69, in _write
os.replace(tmp_path, self.db_path)
FileNotFoundError: [Errno 2] No such file or directory: ‚/data/db.json.tmp‘ → ‚/data/db.json‘
[ERROR] Reason: Worker failed to boot.
Wann tritt es auf?
- Beim allerersten Start (noch keine db.json im data-Ordner).
- Später wieder, nachdem ich rund 190 Karten für 3 Geräte angelegt hatte (db.json ca. 2 MB). Das Schreiben dauert dann länger, der Fehler tritt häufiger auf.
Ursache (Vermutung aus dem Code)
Store() wird beim Import in jedem Worker ausgeführt. _migrate_legacy_cards() ruft _mutate() auf, und das schreibt immer, auch wenn nichts zu migrieren ist. Beide Worker schreiben in dieselbe Zwischendatei db.json.tmp: Worker A benennt sie per os.replace() um, Worker B findet sie dann nicht mehr. Das _lock schützt nur innerhalb eines Prozesses, nicht zwischen den Workern. Dasselbe kann theoretisch auch im laufenden Betrieb passieren, wenn zwei Anfragen gleichzeitig bei verschiedenen Workern landen.
Nachgestellt (von Claude)
In einem Test mit sechs parallel schreibenden Prozessen (je 300 Schreibvorgänge) trat der Fehler mit dem Originalcode in jedem Prozess mehrfach auf, einmal auch mit exakt dem Absturz oben. Mit einem eindeutigen Namen der Zwischendatei gab es keinen Fehler mehr.
Workaround
Bei mir läuft es stabil, wenn gunicorn mit --preload gestartet wird (docker-compose.override.yml mit command: gunicorn … --workers 2 --preload …). Dann läuft die Store-Initialisierung nur einmal im Hauptprozess.
Vorschlag für einen Fix
- In store.py, _write(): einen eindeutigen Namen für die Zwischendatei verwenden, z. B.
tmp_path = f"{self.db_path}.{os.getpid()}.tmp" - _migrate_legacy_cards()/_mutate() nur schreiben lassen, wenn sich tatsächlich etwas geändert hat.
- Langfristig eine Dateisperre (z. B. fcntl.flock) statt des reinen Prozess-Locks. Bei zwei Workern kann sonst bei gleichzeitigem Lesen-Ändern-Schreiben eine Änderung verloren gehen.
Nochmal vielen Dank für das super Feature!
Viele Grüße
Jonas