fix(bg): Hintergrundarbeit reisst den Prozess nicht mehr mit

Das Vorschaubild entstand in einer nackten Goroutine. Chis Recoverer haengt
am Anfragebehandler und sieht davon nichts -- eine Panik im Bilddekodierer
nahm den ganzen Go-Prozess mit, ausgeloest durch einen gewoehnlichen
Upload. Die Bibliothek ist seit ef60497 aktuell; das schloss **diese**
Luecke. Dies hier schliesst die **Gattung**.

pkg/bg mit zwei Funktionen, und der Unterschied ist keine Feinheit:

* bg.Go(name, fn)  -- Goroutine, die eine Panik ueberlebt
* bg.Run(name, fn) -- dasselbe ohne eigene Goroutine, fuer Schleifen

Faengt man eine Panik um den **ganzen** Arbeiter ab, ueberlebt der Prozess
-- aber der Arbeiter ist tot, und von da an wird still keine Datei mehr
erkannt. Der OCR-Arbeiter sichert deshalb jeden **Auftrag** einzeln ab.

Umgestellt: Vorschaubild, Bestand nachtragen, Freigabe-Zaehler,
Speicherbereinigung, OCR-Arbeiter. Eine nackte Goroutine bleibt mit
Begruendung im Code -- die Signalbehandlung; ein recover braechte dort
nichts, weil main ohnehin wartete.

Aufgefangene Paniken werden mit Stapel und Herkunft protokolliert. Vier
Tests, darunter der wichtige: Eine Schleife laeuft nach einer Panik im
dritten Durchgang weiter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-09-12 18:29:02 +02:00
1 parent 89f0817dcb
commit c26bcba012
7 files changed
+156 -17

No files matched your search

+5 -2
View File
@@ -24,6 +24,7 @@ import (
"github.com/rs/zerolog/log"
"aether/internal/storage"
"aether/pkg/bg"
)
const (
@@ -254,7 +255,9 @@ func (c *Collector) referencedKeys(ctx context.Context) (map[string]bool, error)
// im Intervall. Der erste Durchlauf ist verzögert, damit er nicht mit dem
// Hochfahren konkurriert.
func (c *Collector) Start(ctx context.Context, onDone func(Result)) {
go func() {
// Über bg.Go: Der GC liest Verzeichnisse und Datenbankzeilen, und ein
// Absturz darin darf nicht den Dienst mitnehmen, den er aufräumen soll.
bg.Go("Speicherbereinigung", func() {
timer := time.NewTimer(5 * time.Minute)
defer timer.Stop()
@@ -282,7 +285,7 @@ func (c *Collector) Start(ctx context.Context, onDone func(Result)) {
}
timer.Reset(Interval)
}
}()
})
}
// DerivedUsage: Wie viel Platz die **abgeleiteten** Daten belegen.