// Package safego starts background goroutines that cannot take the process // down. A panic inside a detached goroutine is fatal for the whole binary in // Go — there is no per-goroutine boundary like net/http provides for request // handlers. Every "fire and forget" goroutine in archivmail (mail sending, // importer runs, boot-resume jobs) therefore goes through Go/Run. package safego import ( "fmt" "log/slog" ) // Run executes fn synchronously and recovers from a panic, logging it with the // given name. It returns true when fn completed without panicking. func Run(logger *slog.Logger, name string, fn func()) (ok bool) { defer func() { if r := recover(); r != nil { ok = false if logger != nil { logger.Error("recovered from panic in background task", "task", name, "panic", fmt.Sprintf("%v", r)) } } }() fn() return true } // Go runs fn in a new goroutine, recovering and logging any panic. func Go(logger *slog.Logger, name string, fn func()) { go Run(logger, name, fn) //nolint:errcheck // result is irrelevant for detached tasks }