You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:Tornado IOLoop调用stop时出现卡顿的原因是什么?

问题成因分析:Tornado IOLoop停止时的WebSocket连接卡顿

你遇到的这个4-5分钟卡顿问题,核心原因是Tornado的IOLoop.stop()并不会主动清理活跃的WebSocket长连接,它的默认逻辑和TCP连接的特性共同导致了这个延迟:

1. IOLoop.stop()的本质逻辑

ioloop.call_later(3600, ioloop.stop)触发的停止操作,并不是“立即终止”——它只会告诉IOLoop停止接收新的事件,但会等待当前所有正在处理的I/O操作、已注册的回调全部完成后,才会真正退出进程。

而WebSocket是长连接,会持续占用I/O资源并注册读/写回调。如果这些连接没有被主动关闭,IOLoop会一直等待这些连接的I/O事件处理完毕,哪怕此时已经触发了stop指令。

2. WebSocket连接未被主动清理导致的堆积

当你触发IOLoop.stop()时,如果客户端还在向WebSocket连接发送数据(或者网络异常导致连接处于半开状态),操作系统的recv-Q会堆积未读取的消息。Tornado的IOLoop会持续尝试读取这些堆积的数据并处理对应的回调,这个过程会一直持续到:

  • 连接被主动关闭(客户端或服务器发送关闭帧)
  • TCP连接超时(系统默认的TCP超时通常就是几分钟,正好对应你看到的4-5分钟卡顿)

这就是为什么卡顿发生时,netstat能看到WebSocket连接仍处于打开状态且recv-Q有消息堆积——IOLoop在等待这些连接的I/O操作完成,无法正常退出。

3. TCP连接的状态延迟

即使服务器端想关闭连接,TCP层可能处于半关闭状态(比如服务器发送了FIN包,但客户端未响应ACK),或者连接进入TIME_WAIT状态。这些状态的超时清理由操作系统控制,短则几十秒,长则几分钟,这也会拉长IOLoop的退出时间。


快速解决建议

要避免这个卡顿,你需要在调用IOLoop.stop()前主动关闭所有活跃的WebSocket连接:

  1. 维护一个全局集合来追踪所有活跃的WebSocket连接:
active_websockets = set()

class MyWebSocketHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        active_websockets.add(self)
    
    def on_close(self):
        active_websockets.remove(self)
  1. 替换原来的停止逻辑,先主动关闭连接再停止IOLoop:
def graceful_shutdown():
    # 给所有客户端发送关闭帧,主动断开连接
    for ws in list(active_websockets):
        ws.close(code=1001, reason="Server is restarting")
    # 等待1秒让关闭帧发送完成,再停止IOLoop
    ioloop.call_later(1, ioloop.stop)

# 用优雅关闭替换直接停止
ioloop.call_later(3600, graceful_shutdown)

这样能确保IOLoop在停止前,所有WebSocket连接都被主动清理,不会因为等待未完成的I/O操作而卡顿。

内容的提问来源于stack exchange,提问作者PeopleMoutainPeopleSea

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:10:33