Repository navigation
v2.2.10
fix: 修复面板与 Agent 的 HTTP 连接泄漏(内存/goroutine/fd 持续增长)
现象:面板进程与节点 Agent 的内存随时间持续上涨,久跑后 fd 耗尽、响应变慢。
根因(同一个错误模式,三处):
-
internal/docker/engine.go 的 NewLocalClient 每次调用都新建一个
http.Transport,而 Transport 会为每条空闲连接常驻 readLoop/writeLoop 两个
goroutine;goroutine 是 GC 根并反向引用 Transport,于是「用完就丢」的
Transport 永远不会被回收——连接不关、goroutine 不退、收发缓冲区不释放。
零值 Transport 的 IdleConnTimeout 还是 0,连空闲超时兜底都没有。
触发点:- 面板每 30s 的本机节点刷新(EnsureLocalNode → DetectLocal)
- 面板每 30s 的应用状态探测(AppStatesByNode → EngineFor,每个节点各一个)
- 面板每次 Docker 状态/日志/安装操作(EngineFor)
- Agent 每次 Docker 代理请求(/v1/docker/*)
- Agent 每 30s 一次心跳(Heartbeat → DetectLocal)
-
internal/agent/client.go 的 doJSON 在配置了证书指纹固定(pin)或
OCP_INSECURE_TLS 时,每次心跳都新建一个带自定义 TLS 的 Transport,
同样永不回收(还额外挂着 TLS 缓冲区)。实测(50 轮探测):旧实现 goroutine 3 → 153(+150,每次 +3);
改为共享客户端后 156 → 162(+6,纯噪声)。
修复:
- docker 包新增 LocalHTTPClient(socket):按 socket 复用的进程内单例客户端,
并显式设置 MaxIdleConns/MaxIdleConnsPerHost=16、IdleConnTimeout=90s;
NewLocal 改走共享池;NewLocalClient 保留原语义(独立客户端)并补注释要求
调用方 CloseIdleConnections。EngineFor 与 docker 代理改用共享客户端。 - Agent 的 doJSON 改为按 (insecure, pin) 缓存客户端;无 pin/insecure 时直接
复用 net/http 的共享 Transport。另注意:值为 nil 的 *http.Transport 赋给
接口字段会得到「非 nil 接口」,Client 会拿它发请求并 panic,故默认分支保持
Transport 零值。 - 前端:NodesView 卸载时兜底 teardownTerm()。此前只有对话框 @close 会释放,
切路由/组件销毁时 WebSocket 保持打开,服务端那条 SSH 会话与其收发 goroutine
一直挂着,前端 xterm 实例与 ResizeObserver 也留在内存里。
验证:
- 新增 internal/docker 回归用例:同一 socket 必须返回同一客户端实例、
Transport 必须设置 IdleConnTimeout 与连接数上界、用真实 unix socket 连续
探测 100 轮后 goroutine 不得线性增长(修复前每轮 +3)。 - 新增 internal/agent 用例:默认/pin/insecure 三种配置各自缓存,pin 归一化
(去前缀、小写)命中同一实例,不同 pin 互不共用。 - go build / go vet 通过;go test ./... -p 1 全量通过(20 个包);
前端 vite build 通过;面板启动冒烟正常(入口与静态资源均 200)。
完整提交:af09f7e