藏川线前段

--- 摄于 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 时,被动接收方会进入的一个状态,如果一切正常,被动方会响应 ACK + FIN,迁移到 LAST_ACK,然后正常关闭
  • FIN_WAIT_1 主动发出 FIN 时,主动方进入的等待 ACK 的状态
  • FIN_WAIT_2 主动发出 FIN 后,接到 ACK 后,等待对方发出 FIN 的状态

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

结合 backlog 定位问题

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

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

  • 应用是在 tokio 异步运行时内的,是否存在大量代码执行 blocking 操作占用了 worker,导致调度失灵,无法进行 accept 操作,进而导致 backlog 被塞满
  • 是否因为业务任务过多,而端口被 dos,导致 backlog 的插入速度远大于获取速度,进而导致一直无法恢复正常状态
  • 是否可能存在 bug,导致 backlog 的插入事件无法通知到 accept 操作进行,进而导致了 backlog 无法清理的问题

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

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

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

小结

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

评论区

加载更多

登录后评论