lolly/docs/superpowers/plans/2026-06-11-deep-review-fixes.md
xfy fe0dee4da3 fix(compression,ssl,server,lua): resolve data races and concurrency bugs
- 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
2026-06-11 16:30:11 +08:00

7.3 KiB
Raw Blame History

深度审查修复计划

目标

修复 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
  • 修复: 预先分配新的 slicereq := 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) > 0 checkstartSingleMode 同理
  • 测试: 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) > 0 guard
  • 测试: 空 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
  • 问题: executeTimerCancel 都递减 active向 closed callbackQueue 发送会 panic
  • 修复: 只在 executeTimer defer 中递减 activeCancel 仅关闭 cancel channelcallbackQueue 发送加锁保护
  • 测试: 并发 Cancel 和触发不 panicactive 不会为负

2.5 lua/api_socket_tcp ConnectAsync race

  • 文件: internal/lua/api_socket_tcp.go:196-205, 640-642
  • 问题: goroutine 设置 s.currentOp = nilcaller 访问 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() 已执行
  • 修复: 移除 deferlinuxSendfile 负责关闭;或重构 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 错误时检查 stopChStop() 设置 udpSrv.running.Store(false)
  • 测试: UDP server Stop 能正常完成

3.6 stream/stream.go upstream name mismatch

  • 文件: internal/stream/stream.go:596-608
  • 问题: AddUpstream 用 namehandleConnection 用 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 不停止;StopCleanup double-close panic
  • 修复: sync.Once 保护 closereload 时调用 StopCleanup
  • 测试: 多次创建/销毁 limiter 不泄漏 goroutine不 panic

批次 4High severity

4.1 middleware/security/headers.go nil cfg

  • UpdateConfigaddHeaders 中做 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 只生成一次并存入 UserValuetime_local 用 -0700

4.9 logging.go file handle leak

  • New() 中保存 *os.File 以便 Close() 关闭

验证

每个批次完成后:

  1. go test -race 针对修改的包
  2. make lint
  3. go test ./internal/...
  4. make build

全部完成后做一次全量验证。