Files
lky-spec 3ca95b294d feat: port NOFXi agent module onto latest dev base (#1485)
* feat: integrate NOFXi agent into dev

* Enhance NOFXi agent workflow and diagnostics
2026-04-21 23:47:55 +08:00

60 lines
1.6 KiB
Go

// Package safe provides panic-recovery wrappers for goroutines.
// A panic in any bare goroutine tears down the entire process.
// Use safe.Go instead of `go func()` in long-running or critical paths.
package safe
import (
"fmt"
"nofx/logger"
"runtime/debug"
)
// Go launches fn in a new goroutine with automatic panic recovery.
// If fn panics, the panic is logged (with stack trace) but the process
// continues running. An optional onPanic callback receives the recovered value.
func Go(fn func(), onPanic ...func(recovered interface{})) {
go func() {
defer func() {
if r := recover(); r != nil {
stack := string(debug.Stack())
logger.Errorf("🔥 goroutine panic recovered: %v\n%s", r, stack)
for _, cb := range onPanic {
func() {
defer func() {
if r2 := recover(); r2 != nil {
logger.Errorf("🔥 onPanic callback itself panicked: %v", r2)
}
}()
cb(r)
}()
}
}
}()
fn()
}()
}
// GoNamed is like Go but tags the log line with a human-readable name.
func GoNamed(name string, fn func(), onPanic ...func(recovered interface{})) {
Go(func() {
fn()
}, append([]func(interface{}){
func(r interface{}) {
logger.Errorf("🔥 [%s] goroutine panicked: %v", name, r)
},
}, onPanic...)...)
}
// Must converts a panic into an error. Useful inside goroutines where you
// want to handle panics as errors in the caller's recovery flow.
func Must(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v\n%s", r, debug.Stack())
}
}()
fn()
return nil
}