Skip to content

[Bug] 反向 WS 客户端重连后所有主动发送消息失败(ApiNotAvailable):连接注册被旧连接收尾误删,建议在适配器内修复 #10154

Description

@muqing-kg

问题描述

反向 WebSocket(OneBot v11,X-Client-Role: universal)客户端因网络抖动重连后(相同 X-Self-ID),AstrBot 的事件通道正常(消息事件持续上报),但所有主动发送消息(回复、插件推送等)立即失败,且无法自愈,直到重启 AstrBot 才恢复。

失败特征:调用 send_group_msg / send_private_msg 时瞬间抛出 ApiNotAvailable,其 str() 为空,日志表现为空错误(如「发送失败: 」「主动推送发送失败: ..., error=」),失败发生在事件到达后 10ms 量级,无网络往返。

根因

AstrBot 适配器使用的反向 WS 生命周期存在竞态:连接注册表按 X-Self-ID 为键。客户端重连时,新连接覆盖旧条目;随后旧连接的服务端处理器延迟收尾(半开 TCP 场景可达数分钟),收尾时无条件删除该键——实际删掉的是新连接的注册。事件通道按连接对象管理(set + discard)不受影响,因此表现为「收得到、发不出」,且永久失效直至重启。

时序:

  1. 连接 A(X-Self-ID: 123)注册
  2. 连接 A 断网,客户端 ping 超时后重连
  3. 连接 B(同 X-Self-ID)注册,覆盖 A
  4. A 的服务端处理器最终收尾,按键删除,删掉 B 的注册
  5. 此后所有主动发送查表失败 → ApiNotAvailable

修复方案

astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py 内按连接对象身份清理注册(已在生产环境验证有效,观察期零失败):

import aiocqhttp
from quart import websocket


def _remove_wsr_api_client(self) -> None:
    self_id = websocket.headers['X-Self-ID']
    ws = websocket._get_current_object()
    # 仅清理本连接的注册;同 self_id 可能已被新连接覆盖
    if self._wsr_api_clients.get(self_id) is ws:
        del self._wsr_api_clients[self_id]


aiocqhttp.CQHttp._remove_wsr_api_client = _remove_wsr_api_client

复现条件

  • 反向 WS(universal 角色)接入,客户端以相同 X-Self-ID 重连(常见 OneBot v11 实现均如此)
  • 旧连接在服务端延迟收尾(半开 TCP 场景,收敛可达数分钟)
  • 触发一次后发送通道即永久失效,只能重启恢复

环境

  • AstrBot 4.28.1(docker 部署),Python 3.12,反向 WS universal 角色

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions