diff --git a/_build/_cmsg4.txt b/_build/_cmsg4.txt deleted file mode 100644 index 3df3d53..0000000 --- a/_build/_cmsg4.txt +++ /dev/null @@ -1,164 +0,0 @@ -修「经中转几次后永久哑掉」——「按一下没反应」的真因 - -== 现象 == - -用户报: - * 局域网能搜到电脑,IP/端口/配对码都对,连上后**没有任何反应** - * 走中转「已配对」,但点按钮没反应 - -局域网那一半已经修过(preferLan)。这次抓到的是**经中转**那一半, -而且比预想的严重得多。 - -== 证据链(插桩复现出来的)== - -被控端日志里直接抛了未捕获异常,线程当场死: - - Exception in thread client-relay: - server.py:407 session.send(reply) - relay.py:166 self._link.send_to_client(data) - relay.py:357 self._post("/v1/agent/send", ...) - relay.py:766 raise RelayError(f"请求失败: {last_error}") - lanremote.relay.RelayError: 请求失败: Idle - -之后的状态: - * client-relay 线程没了 —— 电脑再也不往手表回任何东西 - * relay-link 线程还活着、还在长轮询 —— 中转那边看着「设备在线」 - * **手表看到的就是「连上了,但按一下毫无反应」** - -写入仓库的测试 _tests/test_relay_local.py 一直是这个形状: -前面几项 PASS、跑到经中转发命令就卡死。之前没人深挖。 - -== 根因 1:一条 HTTP/1.1 连接被两个线程同时用 == - -_post() 里锁只包住「取连接」那一段: - - with self._http_lock: - connection = self._http # 锁到这里就放了 - try: - connection.request("POST", ...) # 锁外发请求 - -于是两个线程会同时用同一条 connection: - * relay-link 线程跑 /v1/agent/poll —— 长轮询,一条请求挂 180 秒 - * client-relay 线程同时 POST /v1/agent/send - -HTTP/1.1 一条连接同一时刻只能有一个在途请求,第二个线程一进来 -就撞上 http.client 状态机,抛 CannotSendRequest("Idle")。 - -_post 的「重试一次」也救不了:它 _drop_http() 之后,另一个线程 -手里还攥着那条旧连接对象在发,照样坏。 - -修法: - * **发命令用独立连接**(_post 加 dedicated 参数)。 - 长轮询那条常年被占着,发命令不该去挤它,各走各的。 - * 两条连接各自一把锁**锁住整个「发请求 + 读响应」周期**, - 不再只锁取连接那一下。 - * 普通请求若发现缓存连接正忙(长轮询在途),临时另开一条用完即关。 - * _drop_http(dedicated=) 区分清哪条;finally 里一定归还占用 - (否则那条连接会被永久标记为忙)。 - -== 根因 2:回包失败会打死整个会话线程 == - -server.py 的 _client_loop 里: - - if reply is not None: - session.send(reply) # <- 不在任何 try 里 - ... - except OSError: # <- 外层只捕获 OSError - pass - -而中转链路上 send 抛的是 RelayError,直接冒出去把线程打死。 -**死一次就永久哑**,代价太大。 - -修法:session.send() 单独兜住所有异常,失败就断开这个会话 -(让手表重连),绝不让线程消失。外层也补一个兜底 except。 - -== 修复效果 == - - 修复前:pair OK -> ping OK -> ping 卡死 -> 线程崩溃,永久哑 - 修复后:pair 21ms -> ping 82ms -> ping 4ms -> ping 6ms,零异常 - -_tests/test_relay_local.py:**之前完全卡死 -> 现在 21/21 全过** - -这个测试之前一直是红的,说明「35 个阶段的回归全绿」这件事 -本身就不成立过 —— 某个阶段卡住时看起来像环境问题而不是缺陷。 - -== 同批修掉验证阶段找到的其它问题 == - -1. **TTL 清理 + 关掉接入开关 = 设备永久锁死**(严重) - register() 用 `device_id in self.devices` 判断老设备,但超时清理 - 把设备从 devices 和 codes 都删了。电脑关机过夜(必然 > 6 小时) - 第二天开机就会被自己的开关挡住,而且**重启也救不回来** - (清 codes 时没落盘,重启后又当孤儿清一遍,自我维系)。 - 管理员在页面上还看不到这台设备,不知道该放开谁。 - 修法:状态文件里单独存 knownDevices 设备历史,超时清理绝不动它; - register 改查这份历史;清理后补 _save();delete_device 才真正忘记。 - -2. **管理页 JS 语法错误 -> 所有按钮失效**(严重) - admin.py 里 ADMIN_JS 是普通三引号字符串,我在 Python 源码里写的 - `\n` 被变成真实换行,拼进 JS 就是字符串里的裸换行 -> - 整段