Skip to content

[BUG] 8.x 移除 Highway TCP 通道后,群文件上传大文件稳定失败(对照实验 3/3 vs 0/3) #862

Description

@Tomato-0914

系统版本

Debian GNU/Linux 13 (trixie) x86_64,腾讯云轻量应用服务器(ap-guangzhou),出口带宽 3 Mbps

QQNT 版本

3.2.33-52892

LLBot 版本

8.2.1(有头模式 ./llbot --pmhq,PMHQ v8.1.1)

OneBot 客户端

为排除框架干扰,测试直接用 ws 客户端连 LLBot 的正向 WebSocket(127.0.0.1:3001)调用 upload_group_file,未经过任何机器人框架。 原始问题也稳定复现于 TRSS-Yunzai 3.1.3。

发生了什么?

从 7.12.x 升级到 8.x 之后,upload_group_file 上传较大文件(本次测试 50MB)稳定失败;7.12.x 时期同样的文件可以正常上传。

定位到 src/ntqqapi/api/file.ts 的 uploadGroupFile:8.x 删除了 HighwayTcpSession,只保留 HTTP 分块上传。

v7.12.15

try {
  await new HighwayTcpSession(trans).upload()
} catch {
  await new HighwayHttpSession(trans).upload()
}

v8.2.1

await new HighwayHttpSession(trans).upload()

src/ntqqapi/helper/highway.ts 里 HighwayTcpSession 类也整个不存在了(文件从 192 行增至 472 行,同时新增 concurrency = 3 与每请求 timeout: 11 * 1000)。

为验证这是否就是直接原因,我在 8.2.1 的构建产物里照 v7 的实现补回了一个 HighwayTcpSession,并把 uploadGroupFile 改回「TCP 优先、失败回退 HTTP」,然后做了对照实验。

对照实验结果

条件:同一台机器、同一个群、同一份 OneBot 调用;每一轮都重新生成一个全新的 50MB 随机文件(head -c 52428800 /dev/urandom,md5 各不相同,避免服务端 fileExist 秒传导致上传分支被跳过);两组在同一个半小时内完成。

组 通道 结果 耗时 失败位置
打补丁 1 TCP 成功 153.3s —
打补丁 2 TCP 成功 154.8s —
打补丁 3 TCP 成功 154.3s —
原版 1 HTTP 失败 125.9s offset 2097152(2 MiB)— Highway request timeout (11s)
原版 2 HTTP 失败 141.6s offset 8388608(8 MiB)— Highway request timeout (11s)
原版 3 HTTP 失败 137.7s offset 42991616(41 MiB)— HTTP Upload failed with code 102902

TCP 3/3 成功且耗时高度一致(153~155 秒,正好是 50MB 跑满 3 Mbps 出口的时间);HTTP 0/3。

两点补充观察

1. 失败位置是随机的,不存在固定阈值

三次分别断在 2 MiB、8 MiB、41 MiB。我最初在另一台机器上连续四次都断在 offset 42991616,一度以为存在「41 MiB 硬上限」,但扩大样本后确认那只是巧合。这与 #829 报告的「50多M-60多M的任意大小处断开」一致。

2. OneBot 层返回的 retcode=1200 请重试 会掩盖真实原因

三次失败在 OneBot 层都是 {"status":"failed","retcode":1200,"wording":"请重试"}。由于测试文件是纯随机二进制(不含任何可被内容审核的信息)却同样触发,可以确认这个提示与内容审核无关,真正原因只体现在 LLBot 日志的 Highway 明细里。建议把 Highway 的错误信息透传到 OneBot 响应中,否则使用者很容易误判为账号风控或内容问题。

关于 #829

#829 报告的也是 102902,被标记为「v8.1.4 已修复」并关闭。但查修复提交 def580ed 的 diff,实质改动只有两行:

-  readonly concurrency = 4
+  readonly concurrency = 3

-        timeout: 10 * 1000,
+        timeout: 11 * 1000,

其余为代码格式调整。这降低了触发概率但未消除问题,在 8.2.1 上仍然稳定复现(本次 0/3)。

如何复现

  1. 准备一个 50MB 的随机文件(务必每次重新生成,否则服务端 fileExist 会秒传,根本不会走 Highway):
    head -c 52428800 /dev/urandom > /tmp/test-50MB.bin
  2. 通过 OneBot 调用上传到群:
    { "action": "upload_group_file",
      "params": { "group_id": 123456789, "file": "/tmp/test-50MB.bin", "name": "test-50MB.bin" } }
  3. 重复 3 次,每次换一个全新随机文件。

在 3 Mbps 出口的机器上 3/3 失败。出口带宽越低、单次上传耗时越长,越容易触发。

期望的结果?

upload_group_file 能稳定上传大文件,与 7.12.x 行为一致。

具体建议,按优先级:

  1. 恢复 TCP 通道,或至少在 HTTP 失败后回退到 TCP。这是最直接的修复,实测 3/3 成功。
  2. 如果要保留纯 HTTP 方案,11 秒的单请求超时对慢出口链路过于激进 —— 1MB 的块在 3 Mbps 链路上理论最快就要 2.7 秒,叠加并发 3 后单请求的实际耗时很容易逼近甚至超过 11 秒。建议改成可配置,或按测得的吞吐动态计算。
  3. 102902 应纳入重试。目前重试白名单只有 request timeout / read ECONNRESET / not a valid proto,错误码直接抛出,不换服务器也不降级。
  4. 把 Highway 的错误明细透传到 OneBot 响应,不要一律包装成「请重试」。

另外提一个 7.x 里存在的小缺陷,恢复 TCP 通道时值得一并处理:v7 的 try { TCP } catch { HTTP } 两条路径复用同一个 readable。若 TCP 传到一半失败,流已被部分消费,回退 HTTP 时会从中间继续读,产出的文件是残缺的。正确做法是每次尝试都重建 createReadStream。

LLBot 运行日志

# ---------- 原版 8.2.1(HTTP 通道),三次连续失败 ----------
第1次 offset 2 MiB
[E] onebot11-adapter 发生错误 Error: [Highway] httpUpload Error uploading block at offset 2097152: Highway request timeout (11s) on http://157.148.54.88:443/cgi-bin/httpconn?htcmd=0x6FF0087&uin=<uin>
    at upload (/root/LLBot-CLI-linux/bin/src/ntqqapi/helper/highway.ts:370:17)
    at async HighwayHttpSession.uploadBlockWithRetry (/root/LLBot-CLI-linux/bin/src/ntqqapi/helper/highway.ts:374:5)
    at async worker (/root/LLBot-CLI-linux/bin/src/ntqqapi/helper/highway.ts:290:11)
    at async HighwayHttpSession.upload (/root/LLBot-CLI-linux/bin/src/ntqqapi/helper/highway.ts:302:5)

第2次 offset 8 MiB
[E] onebot11-adapter 发生错误 Error: [Highway] httpUpload Error uploading block at offset 8388608: Highway request timeout (11s) on http://157.148.36.32:443/cgi-bin/httpconn?htcmd=0x6FF0087&uin=<uin>

第3次 offset 41 MiB
[E] onebot11-adapter 发生错误 Error: [Highway] httpUpload Error uploading block at offset 42991616: HTTP Upload failed with code 102902

# ---------- 补回 TCP 通道后,三次连续成功 ----------
[W] highway [TCP-PATCH] 群文件经 TCP 通道上传成功,耗时 152.2s
[W] highway [TCP-PATCH] 群文件经 TCP 通道上传成功,耗时 153.1s
[W] highway [TCP-PATCH] 群文件经 TCP 通道上传成功,耗时 152.8s

OneBot 客户端运行日志

# ---------- 原版 8.2.1 ----------
=== HTTP : 连续 3 次,每次全新随机文件 ===
[22:38:34] 第1次 文件已生成 md5=7a59998a1b62
[22:40:40] 第1次: 失败  125.9s  retcode=1200  [Highway] httpUpload Error uploading block at offset 2097152: Highway request timeout (11s)
[22:40:45] 第2次 文件已生成 md5=f0b646228df7
[22:43:07] 第2次: 失败  141.6s  retcode=1200  [Highway] httpUpload Error uploading block at offset 8388608: Highway request timeout (11s)
[22:43:12] 第3次 文件已生成 md5=b9347d5bc568
[22:45:30] 第3次: 失败  137.7s  retcode=1200  [Highway] httpUpload Error uploading block at offset 42991616: HTTP Upload failed with code 102902
真实成功 0 / 失败 3 / 共 3

# ---------- 补回 TCP 通道后 ----------
=== TCP : 连续 3 次,每次全新随机文件 ===
[22:28:38] 第1次 文件已生成 md5=74f2532f980e
[22:31:11] 第1次: 成功  153.3s  retcode=0
[22:31:18] 第2次 文件已生成 md5=72bde9d3bff2
[22:33:52] 第2次: 成功  154.8s  retcode=0
[22:33:59] 第3次 文件已生成 md5=f35a14eca0d9
[22:36:33] 第3次: 成功  154.3s  retcode=0
真实成功 3 / 失败 0 / 共 3


## 附:无法通过降级规避

考虑到 8.x 的这个回归,使用者理论上可以退回 7.12.x,但这条路目前是断的:

- v7 的 PMHQ(v7.3.2)不内嵌任何 QQ 版本的偏移数据,运行时从 `http://pmhq.luckylillia.com/a.bin` 获取,而**该域名目前解析为 NXDOMAIN**,导致 7.12.15 配当前版本 QQ 直接报「未找到当前 QQ 版本 (52892) 的偏移数据」而无法启动。
- 尝试把 8.x 的 PMHQ v8.1.1 换入 7.12.15:注入本身成功(能正确识别 QQ 3.2.33-52892),但 `llbot.js` 与注入器之间的 WS 协议跨大版本不兼容,表现为 `checkLogin` 读到 undefined、`pmhq ws send: wait result timeout`、`/api/login-qrcode` 返回 500。

也就是说受影响的用户没有可行的回退方案,只能等待修复或自行修改构建产物。

(另:`api-auth.luckylillia.com` 的启动预检从中国大陆访问存在间歇性超时,重试通常可通过,与本 issue 无关,顺带一提。)

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions