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:
parent
9b8ce2a08a
commit
728a9f454b
@ -187,6 +187,9 @@ func (a *App) reloadConfig() {
|
|||||||
for i, ln := range listeners {
|
for i, ln := range listeners {
|
||||||
duped[i], err = server.DupListener(ln)
|
duped[i], err = server.DupListener(ln)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
for j := 0; j < i; j++ {
|
||||||
|
_ = duped[j].Close()
|
||||||
|
}
|
||||||
a.logger.Error().Err(err).Msg("Failed to dup listener for reload")
|
a.logger.Error().Err(err).Msg("Failed to dup listener for reload")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -205,6 +208,11 @@ func (a *App) reloadConfig() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
reloadTimeout := a.cfg.Shutdown.ReloadTimeout
|
||||||
|
if reloadTimeout <= 0 {
|
||||||
|
reloadTimeout = 5 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case err := <-startErr:
|
case err := <-startErr:
|
||||||
a.logger.Error().Err(err).Msg("Failed to start new server with reloaded config")
|
a.logger.Error().Err(err).Msg("Failed to start new server with reloaded config")
|
||||||
@ -212,7 +220,7 @@ func (a *App) reloadConfig() {
|
|||||||
_ = ln.Close()
|
_ = ln.Close()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case <-time.After(5 * time.Second):
|
case <-time.After(reloadTimeout):
|
||||||
}
|
}
|
||||||
|
|
||||||
oldSrv := a.srv
|
oldSrv := a.srv
|
||||||
|
|||||||
@ -140,7 +140,10 @@ func Load(path string) (*Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Include) > 0 {
|
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}
|
visited := map[string]bool{absPath: true}
|
||||||
if err := processIncludes(&cfg, filepath.Dir(path), 0, visited); err != nil {
|
if err := processIncludes(&cfg, filepath.Dir(path), 0, visited); err != nil {
|
||||||
return nil, fmt.Errorf("处理配置引入失败: %w", err)
|
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 {
|
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] {
|
if visited[absMatch] {
|
||||||
return fmt.Errorf("检测到循环引入: %s", absMatch)
|
return fmt.Errorf("检测到循环引入: %s", absMatch)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -222,6 +222,7 @@ func DefaultConfig() *Config {
|
|||||||
Shutdown: ShutdownConfig{
|
Shutdown: ShutdownConfig{
|
||||||
GracefulTimeout: 30 * time.Second,
|
GracefulTimeout: 30 * time.Second,
|
||||||
FastTimeout: 5 * time.Second,
|
FastTimeout: 5 * time.Second,
|
||||||
|
ReloadTimeout: 5 * time.Second,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -612,6 +613,7 @@ func GenerateConfigYAML(cfg *Config) ([]byte, error) {
|
|||||||
buf.WriteString("shutdown:\n")
|
buf.WriteString("shutdown:\n")
|
||||||
fmt.Fprintf(&buf, " graceful_timeout: %ds # 优雅停止超时(SIGQUIT),等待活跃请求完成(0=使用默认30s)\n", int(cfg.Shutdown.GracefulTimeout.Seconds()))
|
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, " 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")
|
buf.WriteString("\n")
|
||||||
|
|
||||||
// stream 配置
|
// stream 配置
|
||||||
|
|||||||
@ -181,6 +181,11 @@ type ShutdownConfig struct {
|
|||||||
// 接收到 SIGINT 或 SIGTERM 信号后,等待服务器关闭的最大时间
|
// 接收到 SIGINT 或 SIGTERM 信号后,等待服务器关闭的最大时间
|
||||||
// 默认: 5s(当值为 0 时使用默认值)
|
// 默认: 5s(当值为 0 时使用默认值)
|
||||||
FastTimeout time.Duration `yaml:"fast_timeout"`
|
FastTimeout time.Duration `yaml:"fast_timeout"`
|
||||||
|
|
||||||
|
// ReloadTimeout 热重载启动等待超时(SIGHUP)
|
||||||
|
// 等待新服务器启动完成的最大时间,超时后视为启动成功
|
||||||
|
// 默认: 5s(当值为 0 时使用默认值)
|
||||||
|
ReloadTimeout time.Duration `yaml:"reload_timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolverConfig DNS 解析器配置。
|
// ResolverConfig DNS 解析器配置。
|
||||||
|
|||||||
@ -409,7 +409,11 @@ func (s *Server) tcpAddrMatch(inherited, target string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isAnyAddr(host 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。
|
// DupListener 复制 listener 的文件描述符,返回独立的 listener。
|
||||||
@ -422,12 +426,14 @@ func DupListener(ln net.Listener) (net.Listener, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dup tcp listener: %w", err)
|
return nil, fmt.Errorf("dup tcp listener: %w", err)
|
||||||
}
|
}
|
||||||
|
defer file.Close()
|
||||||
return net.FileListener(file)
|
return net.FileListener(file)
|
||||||
case *net.UnixListener:
|
case *net.UnixListener:
|
||||||
file, err := l.File()
|
file, err := l.File()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dup unix listener: %w", err)
|
return nil, fmt.Errorf("dup unix listener: %w", err)
|
||||||
}
|
}
|
||||||
|
defer file.Close()
|
||||||
return net.FileListener(file)
|
return net.FileListener(file)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported listener type: %T", ln)
|
return nil, fmt.Errorf("unsupported listener type: %T", ln)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user