藏川线前段

--- 摄于 2017 年 9 月 藏川线前段

从 CLOSE_WAIT 状态说起(一)

近期,接到线上反馈,在某些机器上,TCP 连接状态会有 CLOSE_WAIT,并且一直挂着,不会自动关闭,同时出现的问题是,任何尝试 connect 的应用都无法连接成功,tcp 的 backlog 溢出,抓包与之前文章很相似,是不是可以合并成一个问题呢?这个问题虽然可以用重启解决,但是,找不到问题根源,是不行的。

之前那篇文章已经将能得到的现象进行了很多猜测,最后发现在 tokio 1.x 上不重现,于是就没有深究问题在哪,这次花了很多时间去追究问题的起源在哪。

CLOSE_WAIT 状态是什么?

不要将 CLOSE_WAIT 和 FIN_WAIT(1 or 2) 混淆了,这三个状态是完全不一样的行为导致的:

那么,是什么原因可能导致大量连接卡在 CLOSE_WAIT 无法关闭呢?这是一个很神奇的问题,正常来说,接到 FIN 就进入了关闭流程,TCP 整个协议是在 内核中实现的,正常不会被上层应用影响,除非发生了一些奇怪的操作导致了 OS 端流程的卡死。我的目标是找到这个奇怪的操作,虽然我无法接触到环境,但可以通过截图等问题手段看到一些想看的东西,抓包已经不起作用了,需要看看 os 提供的其他信息。

结合 backlog 定位问题

回到最开始的现象描述,backlog 溢出了,这个东西很关键,backlog 是内核的 accept queue,用来存放等待用户端获取的已经完成连接握手的崭新 TCP stream,它的溢出,可以大胆猜测:用户端没有及时进行 accept 操作

导致没有及时 accept 操作的可能性会有很多:

前两个可能性因为我无法接触到生产环境,无法做准确判断,最后一个我倒是可以确认,在旧版本中存在一个这样的 bug 触发概率,并且已经在两个月之前已经修复了,那么,是否有可能该环境的库版本没有升级导致了这个问题呢?

经过确认,该环境确实没有升级网络库的版本,现在暂定是这个 bug 导致的问题。那么用户端不获取 backlog 内的 stream 与 CLOSE_WAIT 有什么关系呢?

之前说过,CLOSE_WAIT 是被动接到 FIN 的一端产生的状态,如果握手成功后,该 stream 一直呆在 backlog 中,对端会因为超时不响应强行断开该连接,就是主动发送 FIN,那么在 backlog 中的 stream 接到就自然进入了 CLOSE_WAIT 状态,并且一直停留在该状态,直到用户从队列中取出,尝试 write 的时候,会报错 Broken pipe,这时候,该 FD 才会被丢弃,如果一直不拿出来,那 FD 占用就一直无法删除,也就是最开始接到的反馈现象。

小结

下一篇说一下这个 bug 的产生原因,是怎么修复的,如何通过 bug 代码复现该问题,为什么能在本地测试中藏匿那么久没有被发现。

评论区

加载更多

登录后评论