- compression: move sync.Pool.New initialization into constructors to eliminate lazy-init race in Get() - ssl/ocsp: copy response fields under RLock before releasing, preventing race with concurrent writers in refreshAll - server: change proxiesMu from sync.Mutex to sync.RWMutex; protect getProxyCacheStats and purge handlers with RLock to prevent races with proxy registration - lua/api_timer: fix double-decrement race in Cancel vs executeTimer by using timer.Stop() result to determine who decrements active - lua/api_socket_tcp: fix nil pointer race in ConnectAsync by checking currentOp under lock before Connect returns
7.3 KiB
7.3 KiB
深度审查修复计划
目标
修复 6 个 review subagent 发现的 Critical 和 High 级别问题。
策略
按影响范围和修复复杂度分 4 个批次执行,每批独立提交。
批次 1:明确的崩溃/数据损坏(Critical,最容易验证)
1.1 logging.go: append 污染 fasthttp buffer
- 文件:
internal/logging/logging.go:147 - 问题:
append(append(ctx.Method(), ' '), ctx.Path()...)可能 mutate fasthttp 内部 buffer - 修复: 预先分配新的 slice:
req := make([]byte, 0, len(method)+1+len(path)) - 测试: 验证 request 字段不修改原始 buffer
1.2 mimeutil/detect.go: 缓存空字符串 bug
- 文件:
internal/mimeutil/detect.go:150 - 问题: 未知扩展名时先插入空字符串再替换成 defaultMIME
- 修复: 在缓存插入前完成 defaultMIME 回退,或用 RLock 读 + Lock 写
- 测试: 多次查找未知扩展名都应返回
application/octet-stream
1.3 server.go nil/empty config panic
- 文件:
internal/server/server.go:270-295, 470 - 问题:
Start()/startSingleMode()访问s.config.*和Servers[0]无前置检查 - 修复:
Start()开头加 nil check 和len(Servers) > 0check;startSingleMode同理 - 测试:
New(nil).Start()应返回 error 而非 panic
1.4 app_common.go empty servers panic
- 文件:
internal/app/app_common.go:176, 202 - 问题:
initHTTP3(),initHTTP2()访问a.cfg.Servers[0]未检查 - 修复: 加
len(a.cfg.Servers) > 0guard - 测试: 空 servers 配置应跳过 HTTP2/3 初始化
1.5 proxy/health.go nil cfg panic
- 文件:
internal/proxy/health.go:80 - 问题:
NewHealthChecker直接读cfg.Interval - 修复:
if cfg == nil { cfg = &config.HealthCheckConfig{} } - 测试:
NewHealthChecker(targets, nil)不 panic
1.6 resolver.go nil cfg panic
- 文件:
internal/resolver/resolver.go:134 - 问题:
New(cfg)读cfg.Enabled - 修复:
if cfg == nil || !cfg.Enabled { return &noopResolver{} } - 测试:
New(nil)不 panic
1.7 lua/api_log.go Fatal kills server
- 文件:
internal/lua/api_log.go:250 - 问题:
EMERG/ALERT/CRIT调用logger.Fatal().Msg()会os.Exit(1) - 修复: 全部映射到非致命的
Error()(Lua 日志级别不应 kill 进程) - 测试: 调用
ngx.log(ngx.EMERG, ...)不退出进程
批次 2:并发 / Race
2.1 compression pool.New race
- 文件:
internal/middleware/compression/compression.go:75-83 - 问题:
compressorPool.Get()懒初始化pool.New - 修复: 在
newGzipPool/newBrotliPool构造时就设置pool.New - 测试:
go test -race多次 Get 不报错
2.2 ssl/ocsp data race
- 文件:
internal/ssl/ocsp.go:397-426 - 问题: RUnlock 后读
resp.status/nextUpdate - 修复: 整个函数体在 RLock 保护下完成,或把可变字段改为 atomic
- 测试:
go test -race ./internal/ssl/...
2.3 server lifecycle/purge race on proxies slice
- 文件:
internal/server/lifecycle.go:222-230,internal/server/purge.go:127,145 - 问题: 无锁读
s.proxies - 修复: 用
s.proxiesMu.RLock()包裹读循环 - 测试: 并行 stats/purge 和 create proxy 时不触发 race
2.4 lua/api_timer active race
- 文件:
internal/lua/api_timer.go:228-265, 303-325 - 问题:
executeTimer和Cancel都递减 active;向 closed callbackQueue 发送会 panic - 修复: 只在
executeTimerdefer 中递减 active;Cancel仅关闭 cancel channel;callbackQueue 发送加锁保护 - 测试: 并发 Cancel 和触发不 panic,
active不会为负
2.5 lua/api_socket_tcp ConnectAsync race
- 文件:
internal/lua/api_socket_tcp.go:196-205, 640-642 - 问题: goroutine 设置
s.currentOp = nil后,caller 访问 op.ID - 修复:
Connect直接返回*SocketOperation给 caller,不要通过字段传递 - 测试: 快速完成连接的 localhost 不触发 nil panic
批次 3:资源泄漏 / 功能损坏
3.1 proxy connection count leak (2 places)
- 文件:
internal/proxy/proxy.go:889-896, 898-919 - 问题: X-Accel-Redirect 和重试路径漏了
DecrementConnections - 修复: 两个 return/continue 前都调用
loadbalance.DecrementConnections(target) - 测试: 内部 redirect 和 retry 后连接计数归零
3.2 server/pool.go deadlock
- 文件:
internal/server/pool.go:178-184 - 问题: queue full 时启动 worker 后立即 blocking send,可能死锁
- 修复: 用非阻塞 send,或在启动 worker 时直接把 task 传给 worker
- 测试: 持续 Submit 满载任务不阻塞
3.3 handler/sendfile_linux.go FD closed before use
- 文件:
internal/handler/sendfile_linux.go:151-169 - 问题:
getSocketFd返回 FD 后defer file.Close()已执行 - 修复: 移除 defer,由
linuxSendfile负责关闭;或重构 syscall 实现 - 测试: Linux sendfile 实际生效
3.4 proxy/websocket.go bufio data loss
- 文件:
internal/proxy/websocket.go:335-349, 415-427 - 问题:
bufio.Reader缓冲的 frame 数据在返回后被丢弃 - 修复: 桥接前 drain
resp.Body,或复用同一个bufio.Reader - 测试: WebSocket 连接不丢首帧
3.5 stream/stream.go UDP shutdown deadlock
- 文件:
internal/stream/stream.go:966-1001 - 问题: UDP serve 在
Stop()后不退出,wg.Wait()死锁 - 修复: 非 timeout 错误时检查
stopCh;Stop()设置udpSrv.running.Store(false) - 测试: UDP server Stop 能正常完成
3.6 stream/stream.go upstream name mismatch
- 文件:
internal/stream/stream.go:596-608 - 问题:
AddUpstream用 name,handleConnection用 listener addr 查表 - 修复: 统一 key,或建立 listener→upstream 映射
- 测试: TCP stream proxy 转发到后端
3.7 ratelimit token bucket cleanup leak
- 文件:
internal/middleware/security/ratelimit.go:132-162, 476-512 - 问题: cleanup goroutine 不停止;
StopCleanupdouble-close panic - 修复:
sync.Once保护 close;reload 时调用 StopCleanup - 测试: 多次创建/销毁 limiter 不泄漏 goroutine,不 panic
批次 4:High severity
4.1 middleware/security/headers.go nil cfg
- 在
UpdateConfig或addHeaders中做 nil guard
4.2 middleware/security/sliding_window.go div by zero
- 构造函数中校验
window > 0
4.3 handler/static.go data race
- setter 和 Handle 并发访问字段;加
sync.RWMutex
4.4 proxy/proxy_dns.go HostClient.Addr race
- DNS 更新时不能并发修改 live client 的 Addr
4.5 loadbalance/slow_start.go Start/Stop bug
stopCh重建、findTarget未初始化
4.6 resolver restart broken
stopCh关闭后不可重用;Start()重新创建
4.7 variable/variable.go nil map panic
- fallback Context 中初始化所有 map
4.8 variable/builtin.go request_id/time_local
- request_id 只生成一次并存入 UserValue;time_local 用
-0700
4.9 logging.go file handle leak
New()中保存*os.File以便Close()关闭
验证
每个批次完成后:
go test -race针对修改的包make lintgo test ./internal/...make build
全部完成后做一次全量验证。