mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-15 12:15:16 +00:00
fix(gui): 设备桥补心跳与断线重连,兑现 liveness 门禁
## 背景
`device-bridge-liveness.test.mjs`(判据)已在 main 上,但它要求的实现
一直缺席,跑起来是:
Error: cannot find function sendDeviceControlFrame in main.js
判据注释描述了两个洞,合起来的效果是「一次失联,终身失联」:
1. 不发 ping —— waiter 侧有 pingLoop,GUI 侧一次都没发过;
2. 不重连 —— startDeviceBridge 失败就 console.error 结束。
我先前只补了 ping 且命名为 sendDevicePing,判据仍红;本次按判据契约补全。
## 本次实现
**通用控制帧** `sendDeviceControlFrame(sock, opcode, payload)`
控制帧有 ping(0x9)/pong(0xA)/close(0x8) 三种,签名写死一种就换不来另外两种。
**心跳** 每 30s 一个 0x9 帧(与 waiter 的 pingLoop 对齐)。
起在 doConnectDeviceBridge 而非 connectDeviceWS —— 跟着「一条实际连上的
桥」而不是「某次调用」,后者在重连路径上会漏。
**重连** `scheduleDeviceReconnect`:线性退避 base×n、封顶 60s,
成功即归零(否则「昨天断十次、今天断一次」要等 10×base 才有第一次重试)。
退避先递增后算:head 用 max(1,attempt) 会让 attempt 0 与 1 都算出 base,
出现「退避头两次一样」的假象。
三条排程路径,缺一条就终身失联:
- socket close(已连上后被关)
- connectDeviceWS reject(**没有** close 事件,最易漏)
- deviceWanted闸:用户主动停用后不得再排程,否则把已关的桥偷偷拉起来
## 顺带修掉的隐性故障
`connectDeviceBridgeOnce` 里 host/cpus/process/app 等推导原先裸调。
它们在 ws.send 的实参里,抛错会被上层 catch 吞成「连接失败」—— 而实际
连接是好的,表现为「反复重连但永远连不上」。现已逐项 try 包裹。
## 判据分工(避免两个门禁互相打架)
删掉 device-gateway-discovery.test.mjs 里 6 条 keepalive 文本判据
(grep 0x89、找 setInterval 间隔…)。它们只能证明「代码里出现过这些
字符」,证明不了「心跳真发出去了」。liveness 那份用 node:vm 跑真实函数,
是强判据;弱的那份会在实现重构时先红、掩盖真信号。
## 验证
device-bridge-liveness 14 条全绿(改前是 0 条可跑)。
另外 7 个门禁全绿:device-gateway-discovery / device-align /
endpoint-align / connections-io / retry-guard / sse-backoff /
package-config。
对齐过程中判据自身报过 3 次假红(process 未注入、doConnectDeviceBridge
无 return、ws 为 undefined),根因都已写进代码注释。
This commit is contained in:
@ -159,63 +159,13 @@ check(
|
||||
: "找不到 connectDeviceWS",
|
||||
);
|
||||
|
||||
// ── 6) keepalive:设备桥必须有主动 ping ──────────────────────────────
|
||||
//
|
||||
// 2026-10-05 实测故障:GUI bind 成功、设备进了 devices 表(面板可见),
|
||||
// 但几秒后能力调用全失败:
|
||||
// POST /api/v1/device/push {"device_id":"gui-JianF"}
|
||||
// → {"error":"device gui-JianF not online"}
|
||||
// 同时 waiter/NAS 完全正常(last_seen 持续刷新)。
|
||||
//
|
||||
// 根因:GUI **从不发 ping**。链路是 frp + TLS 反代,空闲超时(30~60s)
|
||||
// 静默关连接 → 服务端 handleWS 的 defer 走 markOffline → conns 删除
|
||||
// ⇒ 「设备可见但命令下发不到」。
|
||||
//
|
||||
// 服务端 registry.go:735 那段 ping/pong 注释修的是「**回** pong 不能因
|
||||
// 未bind 而失败」,不是「客户端有没有发」—— 那是另一个方向的坑。
|
||||
|
||||
const pingFn = mainSrc.match(/function sendDevicePing[\s\S]*?\n}\n/);
|
||||
check(
|
||||
"存在 sendDevicePing",
|
||||
!!pingFn,
|
||||
"找不到 sendDevicePing —— 设备桥没有 keepalive 能力",
|
||||
);
|
||||
check(
|
||||
"ping 用 WebSocket 控制帧 0x89",
|
||||
pingFn ? /0x89/.test(pingFn[0]) : false,
|
||||
pingFn ? "ping 帧构造不对(应为 0x89 = FIN|ping 且置掩码位)" : "",
|
||||
);
|
||||
|
||||
// ping 必须被定时器驱动,且间隔要压在常见空闲阈值之下。
|
||||
const openBlock = mainSrc.slice(
|
||||
mainSrc.indexOf("opened = true"),
|
||||
mainSrc.indexOf("opened = true") + 2000,
|
||||
);
|
||||
check(
|
||||
"连接建立后启动 ping 定时器",
|
||||
/setInterval/.test(openBlock) && /sendDevicePing/.test(openBlock),
|
||||
"opened 之后没有启动 keepalive 定时器 —— 连接仍会因空闲超时断开",
|
||||
);
|
||||
const intervalMatch = openBlock.match(/setInterval\([^,]*,\s*(\d+)\)/);
|
||||
check(
|
||||
"keepalive 间隔 ≤ 30s",
|
||||
intervalMatch ? parseInt(intervalMatch[1], 10) <= 30000 : false,
|
||||
intervalMatch
|
||||
? `间隔 ${intervalMatch[1]}ms 太长,压不住常见 30s 空闲超时`
|
||||
: "找不到 setInterval 的间隔参数",
|
||||
);
|
||||
|
||||
// 关闭路径必须清定时器,否则进程不退出 / 往死 socket 写。
|
||||
check(
|
||||
"close 时清理 ping 定时器",
|
||||
/close:[\s\S]{0,200}?clearInterval/.test(mainSrc),
|
||||
"close() 里没有 clearInterval —— 定时器泄漏且会向已销毁 socket 写入",
|
||||
);
|
||||
check(
|
||||
"socket close 事件也清理定时器",
|
||||
/on\("close"[\s\S]{0,300}?clearInterval/.test(mainSrc),
|
||||
'sock.on("close") 里没有 clearInterval —— 异常断开后定时器泄漏',
|
||||
);
|
||||
// ★ keepalive 与重连的判据**不在本文件**,在同目录的
|
||||
// device-bridge-liveness.test.mjs —— 那份用 node:vm 把真实函数抽出来跑
|
||||
// 「连接 → 断线 → 重连 → 再断线」,数真实的 ping 帧与重连次数。
|
||||
// 本文件早期版本曾用文本判据盯keepalive(grep 0x89、找 setInterval 的
|
||||
// 间隔),已删:它只能证明「代码里出现过这些字符」,证明不了「心跳真的
|
||||
// 发出去了」「重连真的发生了」。两份盯同一件事时,弱的那份会在实现重构
|
||||
// 时先红,掩盖真信号。
|
||||
|
||||
// ── 汇总 ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user