TokenPLS 补充 HTTP/2 定时器保留连接路径及 mihomo XHTTP 内存影响
TokenPLS 在 Go #81996 中补充指出,仅在 HTTP/2 读循环退出时停止健康检查定时器,仍未覆盖空闲定时器路径:连接在响应体读取期间断开后,forgetStreamID 未检查 cc.closed,可重新启动 IdleConnTimeout;cleanup 在加锁前停止定时器也存在再次启动窗口。他建议连接关闭后禁止重置,并在设置 closed 后再次停止。作者报告,空闲超时为 1 分钟的 16 个连接在关闭 3 秒后,单独应用 defer t.Stop() 仍全部可达,加入补充修改后均不再可达;mihomo XHTTP 的 45 秒 ping 和 300 秒空闲超时会保留连接,曾导致约 50 MB 内存限制的 iOS 隧道每几分钟被系统终止。他提供的带测试回移植 PR 为 MetaCubeX/http #2,核验时仍开放、未合并。
作者原文@TokenPLSThe client half has a second path that
defer t.Stop()doesn't cover.forgetStreamIDre-armscc.idleTimerwithout checkingcc.closed, so when the connection goes away under an open stream (the server or the network drops it while a response body is being read), the stream is forgotten aftercleanuphas run and the idle timer is armed again for the fullIdleConnTimeout.cleanupalso stops the idle timer before takingcc.mu, and a stream forgotten between thatStopandclosed = truere-arms it the same way. For a connection that was never reused, theResetalso stretches the shortMarkDeadtimercleanupinstalls toIdleConnTimeout.Two more lines close it (same code in
net/http/internal/http2andx/net/http2):func (cc *ClientConn) forgetStreamID(id uint32) { ... - if len(cc.streams) == 0 && cc.idleTimer != nil { + if len(cc.streams) == 0 && cc.idleTimer != nil && !cc.closed { cc.idleTimer.Reset(cc.idleTimeout) func (rl *clientConnReadLoop) cleanup() { ... cc.closed = true + if cc.idleTimer != nil { + cc.idleTimer.Stop() + }With
IdleConnTimeoutset to 1 minute and the connection closed under an open response body, 16 of 16 client connections are still reachable 3 s after close with onlydefer t.Stop()applied, and none once these two changes are added.We ran into this through mihomo's xhttp proxy transport, which opens one HTTP/2 connection per proxied stream, with a 45 s ping period and a 300 s idle timeout. On iOS, where the packet tunnel has a memory limit of about 50 MB, the retained connections got the tunnel killed every few minutes. The backport with a test is MetaCubeX/http#2.