lolly/internal/server/lifecycle.go
xfy f1dd8cbeb0 fix: complete Stage 1 deep review fixes
Batch 1 - Crash/data corruption:
- Add nil/empty config guards in server and app
- Protect against fasthttp buffer mutation in logging
- Ensure mimeutil falls back before caching empty MIME
- Make proxy health checker and resolver tolerate nil cfg
- Prevent Lua EMERG/ALERT/CRIT log levels from killing process

Batch 2 - Concurrency/race conditions:
- Add focused race tests for compression pool, ssl/ocsp,
  server proxies slice, Lua timer cancel, and TCP socket connect
- Fix Lua timer active race and socket ConnectAsync race

Batch 3 - Resource leaks/functional damage:
- Track and stop rate limiter cleanup goroutines on shutdown
- Add tests for proxy connection count leaks, server pool deadlock,
  Linux sendfile integrity, WebSocket frame data loss,
  stream UDP stop deadlock, and upstream name mismatch
- Fix stream ListenTCP/upstream lookup and UDP shutdown

Batch 4 - High severity technical debt:
- Add nil guard in security headers middleware
- Validate sliding window divisor and add tests
- Protect static handler fields with existing RWMutex
- Avoid mutating live HostClient.Addr on DNS updates
- Fix slow start Start/Stop and resolver restart
- Initialize variable fallback context maps
- Add tests for request_id/time_local and logging file handle close

Also:
- Relax integration variable performance threshold under race detector
- Update benchmark plan docs for new stream ListenTCP signature

Verification:
- make test: pass
- go test -race -count=1 ./internal/...: pass
- make lint: 0 issues
- make build: success
2026-06-17 09:48:43 +08:00

237 lines
5.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package server 提供 HTTP 服务器的核心实现,支持单服务器、虚拟主机和多服务器三种运行模式。
//
// 包含服务器生命周期管理相关的逻辑,用于处理启动、停止和信号处理。
//
// 作者xfy
package server
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/valyala/fasthttp"
"rua.plus/lolly/internal/logging"
)
// cleanupResources 清理服务器资源。
//
// 停止 Goroutine 池、健康检查器关闭访问日志、TLS 管理器、
// AccessControl 和 Lua 引擎。由 StopWithTimeout 和 GracefulStop 共用。
func (s *Server) cleanupResources() {
// 停止 Goroutine 池
if s.pool != nil {
s.pool.Stop()
}
// 停止健康检查器
s.proxiesMu.Lock()
for _, hc := range s.healthCheckers {
hc.Stop()
}
s.proxiesMu.Unlock()
// 关闭访问日志
if s.accessLogMiddleware != nil {
_ = s.accessLogMiddleware.Close()
}
// 关闭 TLS 管理器
if s.tlsManager != nil {
s.tlsManager.Close()
}
// 关闭 AccessControl (释放 GeoIP 资源)
s.accessControlsMu.Lock()
for _, ac := range s.accessControls {
if err := ac.Close(); err != nil {
logging.Warn().Err(err).Msg("Failed to close AccessControl")
}
}
s.accessControlsMu.Unlock()
// 停止 RateLimiter 的后台清理 goroutine
s.stopRateLimiters()
// 关闭 Lua 引擎
if s.luaEngine != nil {
s.luaEngine.Close()
logging.Info().Msg("Lua engine closed")
}
}
// shutdownServers 并行关闭多个 fasthttp.Server 实例。
//
// 使用 goroutine 并行关闭所有服务器,收集所有错误并返回聚合错误。
// 部分服务器关闭失败不会影响其他服务器的关闭。
//
// 参数:
// - ctx: 关闭上下文,用于控制超时和取消
// - servers: 要关闭的 fasthttp.Server 实例列表
//
// 返回值:
// - error: 聚合错误,无错误或全部成功时返回 nil
func shutdownServers(ctx context.Context, servers []*fasthttp.Server) error {
// 防御性检查nil ctx 使用默认背景
if ctx == nil {
ctx = context.Background()
}
if len(servers) == 0 {
return nil
}
var (
mu sync.Mutex
errs []error
wg sync.WaitGroup
)
for _, srv := range servers {
if srv == nil {
continue
}
wg.Add(1)
go func(s *fasthttp.Server) {
defer wg.Done()
if err := s.Shutdown(); err != nil {
mu.Lock()
errs = append(errs, err)
mu.Unlock()
}
}(srv)
}
// 等待所有关闭完成或上下文取消
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case <-done:
if len(errs) == 0 {
return nil
}
if len(errs) == 1 {
return errs[0]
}
msgs := make([]string, len(errs))
for i, e := range errs {
msgs[i] = e.Error()
}
return fmt.Errorf("failed to close servers: %d errors: %s", len(errs), strings.Join(msgs, "; "))
case <-ctx.Done():
return ctx.Err()
}
}
// StopWithTimeout 快速停止服务器(支持自定义超时)。
//
// 立即停止服务器,不等待正在处理的请求完成。
// 停止所有健康检查器和访问日志中间件。
//
// 参数:
// - timeout: 快速关闭的最大等待时间
//
// 返回值:
// - error: 停止过程中遇到的错误
//
// 注意事项:
// - 对于生产环境,建议使用 GracefulStop 实现优雅关闭
// - timeout <= 0 时会使用默认 5s 超时
func (s *Server) StopWithTimeout(timeout time.Duration) error {
// 防御性检查:如果 timeout <= 0使用默认值
if timeout <= 0 {
timeout = 5 * time.Second
}
s.running.Store(false)
s.cleanupResources()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// 多服务器模式:并行关闭所有 fasthttp.Server
if len(s.fastServers) > 0 {
return shutdownServers(ctx, s.fastServers)
}
// 单服务器模式:关闭单个 fasthttp.Server
if s.fastServer != nil {
done := make(chan struct{})
go func() {
_ = s.fastServer.Shutdown()
close(done)
}()
select {
case <-done:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
return nil
}
// GracefulStop 优雅停止服务器。
//
// 等待正在处理的请求完成后再停止服务器,确保连接正常关闭。
// 如果超时时间到达仍有请求未完成,将返回超时错误。
//
// 参数:
// - timeout: 优雅关闭的最大等待时间
//
// 返回值:
// - error: 停止过程中遇到的错误,超时返回 context.DeadlineExceeded
//
// 注意事项:
// - 推荐在生产环境使用此方法关闭服务器
// - 超时后会强制关闭,可能导致部分请求中断
func (s *Server) GracefulStop(timeout time.Duration) error {
s.running.Store(false)
s.cleanupResources()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// 多服务器模式:并行关闭所有 fasthttp.Server
if len(s.fastServers) > 0 {
return shutdownServers(ctx, s.fastServers)
}
// 单服务器模式:关闭单个 fasthttp.Server
if s.fastServer != nil {
done := make(chan struct{})
go func() {
_ = s.fastServer.Shutdown()
close(done)
}()
select {
case <-done:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
return nil
}
// getProxyCacheStats 收集所有代理缓存的统计信息。
func (s *Server) getProxyCacheStats() ProxyCacheStats {
var total ProxyCacheStats
s.proxiesMu.RLock()
for _, p := range s.proxies {
if stats := p.GetCacheStats(); stats != nil {
total.Entries += stats.Entries
total.Pending += stats.Pending
}
}
s.proxiesMu.RUnlock()
return total
}