// Package bg startet Hintergrundarbeit, die den Prozess nicht mitreißen kann. // // **Der Anlass ist gemessen, nicht theoretisch.** Aether erzeugt das // Vorschaubild einer hochgeladenen Datei in einer eigenen Goroutine. Chis // `Recoverer` hängt am Anfragebehandler und sieht davon nichts – eine Panik // dort nimmt den **ganzen Go-Prozess** mit. Am 12.09.2026 stand dafür sogar // eine passende Lücke offen (GO-2026-5061, Panik im WebP-Dekodierer): Ein // gewöhnlicher Bild-Upload hätte den Server umgelegt. // // Die Bibliothek zu aktualisieren schließt **diese** Lücke. Diese Datei // schließt die **Gattung**: Bilddekodierer, Fremdformate und externe Programme // sind Felder, in denen regelmäßig etwas gefunden wird, und keines davon darf // über einen Weg erreichbar sein, an dessen Ende der Prozess stirbt. // // Was hier *nicht* passiert: Fehler verschlucken. Eine aufgefangene Panik wird // mit Stapel protokolliert. Sie ist ein Fehler und bleibt einer – sie reißt // nur nichts mehr mit. package bg import ( "runtime/debug" "github.com/rs/zerolog/log" ) // Go startet fn in einer Goroutine, die eine Panik überlebt. // // name steht im Logeintrag und sollte sagen, *was* da lief ("Vorschaubild", // "Texterkennung") – eine Zeile „panic recovered" ohne Herkunft hilft um drei // Uhr nachts niemandem. func Go(name string, fn func()) { go Run(name, fn) } // Run ist dasselbe ohne eigene Goroutine: für Schleifen, in denen **ein** // Durchgang scheitern darf, ohne dass die Schleife endet. // // Der Unterschied ist keine Feinheit. Fängt man eine Panik um den ganzen // Arbeiter herum ab, überlebt der Prozess – aber der Arbeiter ist tot, und // von da an wird still keine Datei mehr erkannt. Fängt man sie um den // einzelnen Auftrag ab, verliert man den einen Auftrag und macht weiter. func Run(name string, fn func()) { defer func() { if r := recover(); r != nil { log.Error(). Str("wo", name). Interface("panik", r). Str("stapel", string(debug.Stack())). Msg("Hintergrundarbeit abgestürzt – aufgefangen") } }() fn() }