来自 #136 的架构级评审建议。不阻塞合入,仅供参考是否有更好的架构解法。
💡 [建议 · 资源] StartLogger 泄漏 goroutine(ctx 不取消时永久驻留) pkg/metrics/metrics.go
问题根因:StartLogger 接收 context.Context 但两个 Server main 都传入了 context.Background()——一个永不取消的 context。后台 goroutine 将随进程生命周期永久驻留,进程退出时自然终止。严格来说这并非 bug(进程退出时 OS 回收一切),但会阻碍 goroutine 泄漏检测(如 runtime.NumGoroutine 监控在正常操作中不会看到此计数回落;若 future 有测试或优雅关闭流程,该 goroutine 也会泄漏)。
为什么低级解法不够:简单地给 StartLogger 加一行 defer 没有意义,因为 main 函数不会 return。问题不是「忘记 defer cancel」,而是 API 签名暗示了可取消性但实际调用方从不取消——这是一种契约误导。
架构级方案:方案一(最小改动):在导出函数上明确契约——改成 StartLogger(interval) 不带 ctx,或签名改为 StartLogger(stop <-chan struct{}),消除「可取消」的假承诺。方案二(长远):让 metrics 包持有自己的关闭信号,由 Server 的 Shutdown 路径触发(如 metrics.Stop()),纳入进程生命周期管理。这样既保留未来优雅关闭的可能性,又不让调用方误以为传 Background() 是正确用法。
代价/收益:方案一只改签名,无运行时成本;方案二需 Server 持有一个关闭信号。收益:goroutine 生命周期可管理,泄漏检测不误报,测试可干净退出。
💡 [建议 · 资源] StartLogger 泄漏 goroutine(ctx 不取消时永久驻留)
pkg/metrics/metrics.go问题根因:
StartLogger接收context.Context但两个 Server main 都传入了context.Background()——一个永不取消的 context。后台 goroutine 将随进程生命周期永久驻留,进程退出时自然终止。严格来说这并非 bug(进程退出时 OS 回收一切),但会阻碍 goroutine 泄漏检测(如runtime.NumGoroutine监控在正常操作中不会看到此计数回落;若 future 有测试或优雅关闭流程,该 goroutine 也会泄漏)。为什么低级解法不够:简单地给
StartLogger加一行defer没有意义,因为 main 函数不会 return。问题不是「忘记 defer cancel」,而是 API 签名暗示了可取消性但实际调用方从不取消——这是一种契约误导。架构级方案:方案一(最小改动):在导出函数上明确契约——改成
StartLogger(interval)不带 ctx,或签名改为StartLogger(stop <-chan struct{}),消除「可取消」的假承诺。方案二(长远):让metrics包持有自己的关闭信号,由 Server 的Shutdown路径触发(如metrics.Stop()),纳入进程生命周期管理。这样既保留未来优雅关闭的可能性,又不让调用方误以为传Background()是正确用法。代价/收益:方案一只改签名,无运行时成本;方案二需 Server 持有一个关闭信号。收益:goroutine 生命周期可管理,泄漏检测不误报,测试可干净退出。