现象
8 月 7 号我们发现一个奇怪的 bug:
$ napcat auth qr
Bot is already online: 噗噗 (3914024488)
No QR code needed.
$ napcat status
# 也显示 online但实际 QQ 那边——噗噗根本没回消息。
排查
这种 bug 最讨厌:两个 service 都觉得自己是对的。
第一步:分清楚谁报的状态
napcat auth qr 检查的是“napcat-cli daemon 进程是否在跑”。napcat status 检查的是“daemon 里维护的 _ws_connected 标志位”。
两个标志位互相独立:
- daemon 进程跑着 →
auth说“在线” _ws_connected是True→status说“在线”
但 _ws_connected 只在 WebSocket 连接建立那一刻设为 True,不会在断连时自动更新。
第二步:复现
在 napcat-cli daemon 跑着的时候,去 docker container 里把 napcat 进程 kill 掉——模拟“QQ 协议层断连”。
docker exec napcat pkill -9 -f napcat回到本机:
$ napcat auth qr
Bot is already online: 噗噗 (3914024488) ← daemon 还跑着
$ napcat status
[ok] daemon: running
[ok] ws: connected ← 但 _ws_connected 还是 True两个都说在线,但实际 QQ 已经断。
第三步:根因
_ws_connected 是个全局变量,只在 connect 成功时设为 True,没有对应的“断连时设为 False“的代码。
WebSocket 客户端没有断连回调——这是 napcat-cli 当初写的简化。但这是错的:长期跑的 service 必须有断连检测。
修复
三件事:
- WebSocket client 加
onclose回调,断连时设_ws_connected = False status命令不只看缓存,主动 ping NapCat HTTP API(/get_status),看真实连接状态auth qr在 daemon 报“online”时,主动去 ping NapCat——如果 ping 不通就提示“daemon 认为在跑,但 NapCat 实际已断”
# napcat_cli/daemon/watch.py
async def on_ws_close(*_):
log.warn("[WS] closed")
state.ws_connected = False
state.last_disconnect_at = time.time()
ws.on('close', on_ws_close)经验
- 缓存的“在线”标志必须由事件驱动更新 —— 不能只设一次 True 就完事
- 两个独立 service 互相核对状态时,要有个第三方作为 ground truth —— 这里 NapCat HTTP API 是 ground truth
- 长期 service 必须有 health check —— 启动时的“on”不代表一直“on”
🎵 Listening companion
两个 service 都觉得自己是对的,错的其实是它们之间的“信任”。