3x-ui/internal/util/common/err.go
n0ctal f63ed9f510
fix(jobs): isolate per-node background goroutines from panics (#5397)
A panic in a goroutine without a recover takes the whole panel down. The
per-node heartbeat and traffic-sync goroutines run remote network I/O for
each node with no panic isolation, so one misbehaving node could crash the
master.

Add common.GoRecover(name, fn), which runs fn in a goroutine guarded by a
recover that logs the panic with a stack trace instead of crashing, and use
it for the per-node heartbeat, traffic-sync and global-push goroutines. The
deferred WaitGroup/semaphore releases still run during panic unwind, so the
group never stalls. Other background goroutines can adopt the same helper.
2026-06-20 00:38:25 +02:00

47 lines
1.2 KiB
Go

// Package common provides common utility functions for error handling, formatting, and multi-error management.
package common
import (
"errors"
"fmt"
"runtime/debug"
"github.com/mhsanaei/3x-ui/v3/internal/logger"
)
// NewErrorf creates a new error with formatted message.
func NewErrorf(format string, a ...any) error {
msg := fmt.Sprintf(format, a...)
return errors.New(msg)
}
// NewError creates a new error from the given arguments.
func NewError(a ...any) error {
msg := fmt.Sprintln(a...)
return errors.New(msg)
}
// Recover handles panic recovery and logs the panic error if a message is provided.
func Recover(msg string) any {
panicErr := recover()
if panicErr != nil {
if msg != "" {
logger.Error(msg, "panic:", panicErr)
}
}
return panicErr
}
// GoRecover runs fn in a new goroutine guarded by a recover, so a panic in a
// background goroutine is logged (with name and a stack trace) instead of taking
// the whole process down. name identifies the goroutine in the log.
func GoRecover(name string, fn func()) {
go func() {
defer func() {
if r := recover(); r != nil {
logger.Error("panic in goroutine", name, ":", r, "\n"+string(debug.Stack()))
}
}()
fn()
}()
}