bigywater.tech
gRPC · 35

流的泄漏:忘记关闭流时服务端会怎样

忘记关闭的流会怎么样?GC 管不管?它会不会拖累别的请求?CloseSend、cancel、读到 EOF 到底哪个才算结束?这篇用服务端 handler 计数器和抓包代理逐个验证。

200 个 handler
丢弃引用并 GC 之后,200 个泄漏的流在服务端仍然全部存在
1002.1 ms → 0.7 ms
流泄漏占满名额时 Unary 的耗时,与 cancel 之后的耗时
GOAWAY 0 个
有泄漏的流时,MaxConnectionIdle 到期后服务端仍没有关闭连接

流需要明确结束

实测 · 本机执行输出
  开了 200 个流、没处理:服务端在运行的 handler 200 个;进程 goroutine 比基线多 405
  丢掉所有引用并 runtime.GC() 3 次之后:服务端 handler 200 个;goroutine 比基线多 405
  conn.Close() 之后:服务端 handler 0 个

一个流只有读到服务端结束、取消 ctx 或关闭连接才会结束。不再引用它、等 GC,服务端 handler 不会因此退出。

占着名额

实测 · 本机执行输出
  10 个流被泄漏占满名额:Unary 调用 1002.1 ms 后返回 code=DeadlineExceeded message="context deadline exceeded"
  cancel 掉泄漏的流之后:Unary 调用 0.7 ms 后返回 成功;服务端在运行的 handler 0 个

每个未结束的流占一个并发名额(MaxConcurrentStreams),泄漏的流足够多时,同一连接上别的调用也会排队到超时。

哪些算结束

实测 · 本机执行输出
  只调用 CloseSend():服务端 handler 1 个(还在)
  再开一个并 cancel ctx:服务端 handler 共 1 个(A 那个还在,B 已经释放)
  再开一个会自己结束的流并读到 EOF:服务端 handler 共 1 个(仍只有 A)
  conn.Close() 之后:服务端 handler 0 个

服务端流:读到 EOF 或取消 ctx,CloseSend 不算。客户端流:CloseSend 或 CloseAndRecv,让服务端读到 EOF。最稳妥是为每个流使用 context.WithCancel 并 defer cancel()。

连接保持

实测 · 本机执行输出
  发了 3 条之后没有 CloseAndRecv:服务端 handler 1 个
  调用 CloseAndRecv():成功,服务端数到 3 条;服务端 handler 0 个

泄漏的流让连接一直有在途的流,MaxConnectionIdle 这类空闲超时永远不触发。

排查

实测 · 本机执行输出
  有一个泄漏的流          1.5 秒内服务端发出的 GOAWAY 0 个;TCP 连接数 1
                   cancel 之后再过 0.8 秒:GOAWAY 共 1 个
  没有任何在途的流         1.5 秒内服务端发出的 GOAWAY 1 个;TCP 连接数 1

服务端在运行的 handler 数、goroutine 数持续上涨是泄漏的信号。

分步演示

流的泄漏
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1 / grpc-go v1.84.0 / protobuf-go v1.36.12 / golang.org/x/net v0.57.0 / protoc 36.2(Homebrew);服务端和客户端都是本机起的 Go 进程(127.0.0.1,端口 9800 以上,不访问任何外部服务);需要看线上字节时用我自己写的 TCP 抓包代理加 golang.org/x/net/http2 的 Framer 解析 HTTP/2 帧;计时用 time.Now 或 perf_counter,数字每次运行会有小幅抖动。handler 数用服务端 handler 里的原子计数器;goroutine 数用 runtime.NumGoroutine(客户端和服务端在同一个进程,所以是合计);GOAWAY 来自抓包代理;时间是本机测量
    • 没有验证:双向流泄漏的表现(只测了服务端流和客户端流);客户端 ctx 的 deadline 到期是否同样释放(预期会,没有单独测);泄漏对服务端内存的实际增长(第 31 篇给了每个调用的开销);keepalive 对泄漏流的影响;其它语言的客户端是否有终结器会清理未结束的流
    京ICP备2026067502号-1京公网安备11010502063487号