求助:Tornado IOLoop调用stop时出现卡顿的原因是什么?
你遇到的这个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连接:
- 维护一个全局集合来追踪所有活跃的WebSocket连接:
active_websockets = set() class MyWebSocketHandler(tornado.websocket.WebSocketHandler): def open(self): active_websockets.add(self) def on_close(self): active_websockets.remove(self)
- 替换原来的停止逻辑,先主动关闭连接再停止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

