Files
ykxmz/_build/fix_bench_code.py
T
xmz7891 cc5891e6d0 修局域网/中转对比基准:它自己有两个 bug,害产品背了黑锅
这个基准一直是红的(中转那条路报「连接阶段失败: timed out」),
而且报告里写着"这是中转链路的产品 bug"。**查下来不是产品的问题。**

== bug 1:两条路用了同一个配对码 ==

基准为了排除互相干扰,给两条路**各起一台全新被控端**(设计是对的),
但两台都用了写死的 code="246813"。

中转有「一个配对码只属于一台设备」的规则:
  第 1 台(跑局域网)注册成功
    -> stop_agent 杀掉进程,但**中转还以为它在线**(last_seen 没过期)
  第 2 台(跑中转)用同一个码注册 -> **被拒**
     中转日志:配对码 246813 已被 ... 占用,拒绝 ...
  而 start_agent 只检查"有没有设备在线" —— 第 1 台僵尸还挂着,
  于是**以为新的一台登记成功了**
  -> 没有任何进程在回包 -> 超时

对照实验(新增 _build/verify_code_reuse.py):
  场景 A 同一个码   -> **不通**
  场景 B 各用不同码 -> 通

修法:两条路各用不同码;start_agent 的判据从"有没有在线设备"
      改成"有没有**配对码正好是这一台要用的码**的在线设备"。

== bug 2:attach 用的还是旧码(我第一版补丁也漏了这处)==

relay_connect() 里 `client.attach(code)` 用的还是局域网那个码 ——
attach 到一台已经死掉、但中转以为在线的设备上,pair 自然没人回。

修成 `attach(relay_code)`。

== 顺带更新一段过时注释 ==

原文写着「当前版本经中转只稳定支撑前 1~2 次交互……这是中转链路的
产品 bug」,并据此把中转的轮数调小、超时调短。

那个 bug **已经修好了**(根因:一条 HTTP/1.1 连接被长轮询和发命令
两个线程同时用,撞状态机抛 CannotSendRequest("Idle"),
异常冒到 _client_loop 外面把 client-relay 线程打死)。
注释不更新会误导后人以为产品还有这个问题。

== 修好之后的实测(本地中转,30 次/路)==

    局域网  握手 16ms  成功 30/30 (100%)  中位  0ms  P90  0ms  最大  1ms
    中转    握手 46ms  成功 30/30 (100%)  中位 15ms  P90 29ms  最大 32ms

这是本地中转,量的是**协议本身的开销**(长轮询往返 vs 直连 TCP);
真实跨网络那条腿的 1.0~1.5 秒来自隧道和 4G,是另一回事。

结论:局域网确实是"毫秒级、不耗流量"的那条根本路径 ——
中位 0ms vs 15ms,而且不走任何外网。

踩坑记录:这两处都是"看起来像产品 bug、实际是测试自己的 bug"。
先怀疑测量工具,比先怀疑产品更省时间 ——
上一次(管理页缓存头)也是我拿 curl -I 测 GET 白查了半天。
2026-10-07 18:20:57 +08:00

179 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
r'''修 bench_lan_vs_relay.py:两条路用了同一个配对码。
== 问题 ==
这个基准为了排除互相干扰,给"局域网"和"中转"两条路**各起一台全新被控端**
(这个设计本身是对的)。但两台都用了写死的 `code = "246813"`。
中转有「一个配对码只能属于一台设备」的规则,于是:
第 1 台(跑局域网)用 246813 注册成功
-> stop_agent 杀掉进程,但**中转还以为它在线**(last_seen 没过期)
第 2 台(跑中转)也用 246813 注册
-> **被拒**(码被占),中转日志:配对码 246813 已被 ... 占用,拒绝 ...
而 start_agent 只检查"有没有设备在线" —— 第 1 台还挂着,于是**以为成功了**
-> 接下来 attach 成功(码还绑在第 1 台那个 deviceId 上),
但**没有任何进程在回包** -> "连接阶段失败: timed out"
实测对照(_build/verify_code_reuse.py):
场景 A 同一个码 -> **不通**
场景 B 各用不同码 -> 通
**所以产品是好的,是这个基准脚本在"公正对比"这个目标上自己绊了自己。**
== 顺带修掉两处 ==
1. 那段「当前版本经中转只稳定支撑前 1~2 次交互」的注释是**过时的** ——
那个 bug 已经修好了(根因是一条 HTTP/1.1 连接被长轮询和发命令
两个线程同时用,撞状态机抛 "Idle" 把 client-relay 线程打死)。
注释要更新,否则会误导后人以为产品还有这个问题。
2. start_agent 只判断"有没有设备在线"太松 —— 会被上一台僵尸设备骗过。
加一个更硬的判据:看 deviceId 是不是**这一台**新生成的。
'''
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
SRC = ROOT / "_tests" / "bench_lan_vs_relay.py"
def patch(text, old, new, label):
if old not in text:
print(" ✗ 没找到:%s" % label)
return text, False
if text.count(old) != 1:
print(" ! 出现 %d 次:%s" % (text.count(old), label))
return text, False
print(" ✓ %s" % label)
return text.replace(old, new, 1), True
def main() -> int:
text = SRC.read_text(encoding="utf-8")
ok_all = True
# ------------------------------------------------ 1. 两个码
text, ok = patch(
text,
' code = "246813"',
''' # ⚠ 两条路必须用**不同的**配对码。
# 中转有「一个码只属于一台设备」的规则,而这个基准给每条路
# 各起一台全新被控端(为了排除互相干扰,设计是对的)。
# 要是两台共用一个码,第 2 台的注册会被拒,而第 1 台虽然
# 进程被杀了、中转还以为它在线 —— 于是"看着登记成功、
# 实际没人回包",表现为连接阶段超时。
# 实测对照见 _build/verify_code_reuse.py:同码不通、不同码通。
code = "246813" # 局域网那条路
relay_code = "975310" # 中转那条路,必须不同''',
"两条路用不同的配对码")
ok_all = ok_all and ok
# ------------------------------------------------ 2. relay_connect 用 relay_code
text, ok = patch(
text,
''' reply = client.call({"cmd": "pair", "deviceId": "bench-relay",
"deviceName": "基准测试手表", "code": code},
args.relay_timeout)''',
''' reply = client.call({"cmd": "pair", "deviceId": "bench-relay",
"deviceName": "基准测试手表",
"code": relay_code},
args.relay_timeout)''',
"relay_connect 用 relay_code")
ok_all = ok_all and ok
# ------------------------------------------------ 3. 第二个 start_agent 传码
text, ok = patch(
text,
""" agent_proc, _data = start_agent()
# attach 只是"接入中转",协议层还得 pair 一次才算鉴权完成 ——""",
""" agent_proc, _data = start_agent(relay_code)
# attach 只是"接入中转",协议层还得 pair 一次才算鉴权完成 ——""",
"第二台用 relay_code")
ok_all = ok_all and ok
# ------------------------------------------------ 4. start_agent 接受码 + 硬判据
text, ok = patch(
text,
""" def start_agent():
data = Path(tempfile.mkdtemp(prefix="bench_agent_", dir=str(work)))
store = Store(data)
store.set_pairing_code(code)""",
""" def start_agent(agent_code=None):
agent_code = agent_code or code
data = Path(tempfile.mkdtemp(prefix="bench_agent_", dir=str(work)))
store = Store(data)
store.set_pairing_code(agent_code)""",
"start_agent 接受码参数")
ok_all = ok_all and ok
# 只判断"有没有在线设备"太松:上一台僵尸还挂着就会骗过它。
# 改成看有没有**在线且没被别的码占用**的设备,并且记录 deviceId 做比对。
text, ok = patch(
text,
""" if any(d.get("online") for d in snap.get("devices", [])):
return proc, data
raise RuntimeError("被控端 45 秒内没登记到中转")""",
""" # ⚠ 不能只看"有没有设备在线" ——
# 上一台被控端虽然进程被杀,中转还没超时清理它,
# 于是这里会被僵尸设备骗过,以为新的一台登记成功了。
# 要求:存在一台在线、且配对码正好是**这一台要用的码**
# 的设备。
live = [d for d in snap.get("devices", [])
if d.get("online") and d.get("code") == agent_code]
if live:
return proc, data
raise RuntimeError(
"被控端 45 秒内没登记到中转(码 %s)" % agent_code)""",
"start_agent 的判据改硬")
ok_all = ok_all and ok
# ------------------------------------------------ 5. 更新过时注释
text, ok = patch(
text,
""" # ⚠ 这里的轮数**故意比局域网少**,而且超时给得短。
# 原因:当前版本经中转只稳定支撑前 1~2 次交互,之后被控端
# 就不再回包(详见 _build/diag_relay_leg.py 的时间线:
# attach/pair/ping#1 全 0ms 返回,ping#2 开始 relay 一直回 0 字节)。
# 这是中转链路的**产品 bug**,不是本测试的毛病 ——
# 仓库现成的 _tests/test_relay_local.py 也是同样形状。
# 所以这里只做**有界**测量:拿得到就打点,拿不到就如实报错,
# 绝不让整个基准卡死。""",
""" # 【历史】这里原来写着"经中转只稳定支撑前 1~2 次交互",
# 并据此把轮数调小、超时调短。那个产品 bug **已经修好了**:
#
# 根因是一条 HTTP/1.1 连接被两个线程同时用 ——
# relay-link 线程的 /v1/agent/poll 要挂 180 秒(长轮询),
# client-relay 线程同时 POST /v1/agent/send 发命令,
# HTTP/1.1 一条连接同时只能一个在途请求,撞上状态机就抛
# CannotSendRequest("Idle"),异常冒到 server.py 的
# _client_loop 外面把线程打死 —— 之后电脑再也不回包。
#
# 修法:发命令走独立连接 + 锁盖住整个请求周期 +
# _client_loop 兜住回包异常。_tests/test_relay_local.py
# 从"完全卡死"变成 21/21 全过。
#
# 所以现在两条路用同样的轮数和超时,口径才一致。
# (用户可通过 --relay-rounds / --relay-timeout 自行调整。)""",
"更新过时注释")
ok_all = ok_all and ok
if not ok_all:
print()
print(" 有锚点没匹配,没有写回")
return 1
SRC.write_text(text, encoding="utf-8", newline="\n")
print()
print(" 已写回 bench_lan_vs_relay.py")
return 0
if __name__ == "__main__":
sys.exit(main())