fix(server,app,config): address code review findings

- Fix FD leak in DupListener: close *os.File after net.FileListener
- Add cleanup of partially-duped listeners on DupListener failure
- Make reload timeout configurable via shutdown.reload_timeout
- Handle filepath.Abs errors in processIncludes instead of ignoring
- Use net.ParseIP in isAnyAddr for robust IPv6 support
This commit is contained in:
xfy 2026-06-03 13:16:05 +08:00
parent 9b8ce2a08a
commit 728a9f454b
5 changed files with 31 additions and 4 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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/SIGTERM0=使用默认5s\n", int(cfg.Shutdown.FastTimeout.Seconds()))
fmt.Fprintf(&buf, " reload_timeout: %ds # 热重载启动等待超时SIGHUP0=使用默认5s\n", int(cfg.Shutdown.ReloadTimeout.Seconds()))
buf.WriteString("\n")
// stream 配置

View File

@ -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 解析器配置。

View File

@ -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)