diff --git a/internal/app/app.go b/internal/app/app.go index 987c27c..aa47e68 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -187,6 +187,9 @@ func (a *App) reloadConfig() { for i, ln := range listeners { duped[i], err = server.DupListener(ln) if err != nil { + for j := 0; j < i; j++ { + _ = duped[j].Close() + } a.logger.Error().Err(err).Msg("Failed to dup listener for reload") return } @@ -205,6 +208,11 @@ func (a *App) reloadConfig() { } }() + reloadTimeout := a.cfg.Shutdown.ReloadTimeout + if reloadTimeout <= 0 { + reloadTimeout = 5 * time.Second + } + select { case err := <-startErr: a.logger.Error().Err(err).Msg("Failed to start new server with reloaded config") @@ -212,7 +220,7 @@ func (a *App) reloadConfig() { _ = ln.Close() } return - case <-time.After(5 * time.Second): + case <-time.After(reloadTimeout): } oldSrv := a.srv diff --git a/internal/config/config.go b/internal/config/config.go index 3d49bfc..b790326 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -140,7 +140,10 @@ func Load(path string) (*Config, error) { } if len(cfg.Include) > 0 { - absPath, _ := filepath.Abs(path) + absPath, err := filepath.Abs(path) + if err != nil { + return nil, fmt.Errorf("获取配置文件绝对路径失败: %w", err) + } visited := map[string]bool{absPath: true} if err := processIncludes(&cfg, filepath.Dir(path), 0, visited); err != nil { return nil, fmt.Errorf("处理配置引入失败: %w", err) @@ -176,7 +179,10 @@ func processIncludes(cfg *Config, baseDir string, depth int, visited map[string] } for _, match := range matches { - absMatch, _ := filepath.Abs(match) + absMatch, err := filepath.Abs(match) + if err != nil { + return fmt.Errorf("获取引入文件绝对路径失败 %q: %w", match, err) + } if visited[absMatch] { return fmt.Errorf("检测到循环引入: %s", absMatch) } diff --git a/internal/config/defaults.go b/internal/config/defaults.go index 1f9ec9d..dc2e0e4 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -222,6 +222,7 @@ func DefaultConfig() *Config { Shutdown: ShutdownConfig{ GracefulTimeout: 30 * time.Second, FastTimeout: 5 * time.Second, + ReloadTimeout: 5 * time.Second, }, } } @@ -612,6 +613,7 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) { buf.WriteString("shutdown:\n") fmt.Fprintf(&buf, " graceful_timeout: %ds # 优雅停止超时(SIGQUIT),等待活跃请求完成(0=使用默认30s)\n", int(cfg.Shutdown.GracefulTimeout.Seconds())) fmt.Fprintf(&buf, " fast_timeout: %ds # 快速停止超时(SIGINT/SIGTERM,0=使用默认5s)\n", int(cfg.Shutdown.FastTimeout.Seconds())) + fmt.Fprintf(&buf, " reload_timeout: %ds # 热重载启动等待超时(SIGHUP,0=使用默认5s)\n", int(cfg.Shutdown.ReloadTimeout.Seconds())) buf.WriteString("\n") // stream 配置 diff --git a/internal/config/performance_config.go b/internal/config/performance_config.go index a644264..b2926b0 100644 --- a/internal/config/performance_config.go +++ b/internal/config/performance_config.go @@ -181,6 +181,11 @@ type ShutdownConfig struct { // 接收到 SIGINT 或 SIGTERM 信号后,等待服务器关闭的最大时间 // 默认: 5s(当值为 0 时使用默认值) FastTimeout time.Duration `yaml:"fast_timeout"` + + // ReloadTimeout 热重载启动等待超时(SIGHUP) + // 等待新服务器启动完成的最大时间,超时后视为启动成功 + // 默认: 5s(当值为 0 时使用默认值) + ReloadTimeout time.Duration `yaml:"reload_timeout"` } // ResolverConfig DNS 解析器配置。 diff --git a/internal/server/server.go b/internal/server/server.go index c55747d..5c0e4ce 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -409,7 +409,11 @@ func (s *Server) tcpAddrMatch(inherited, target string) bool { } func isAnyAddr(host string) bool { - return host == "" || host == "0.0.0.0" || host == "::" || host == "[::]" + if host == "" { + return true + } + ip := net.ParseIP(host) + return ip != nil && ip.IsUnspecified() } // DupListener 复制 listener 的文件描述符,返回独立的 listener。 @@ -422,12 +426,14 @@ func DupListener(ln net.Listener) (net.Listener, error) { if err != nil { return nil, fmt.Errorf("dup tcp listener: %w", err) } + defer file.Close() return net.FileListener(file) case *net.UnixListener: file, err := l.File() if err != nil { return nil, fmt.Errorf("dup unix listener: %w", err) } + defer file.Close() return net.FileListener(file) default: return nil, fmt.Errorf("unsupported listener type: %T", ln)