mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-28 13:23:03 +00:00
## 真机实测发现的
Xvfb + Electron + CDP 真跑,发现 `api()` 的 401 分支**无限自我递归**:
第1次: lock=false → 重登 → lock=false → return api() ← 递归
第2次: lock=false → 重登 → lock=false → return api() ← 又递归
…
那把锁的语义本该是「已经重登过一次,别再登」,但它在递归**之前**就被
清掉了 ⇒ 每层递归看到的都是 `false`。真机实测 **fetch 被调 13 次、
重登 12 次**才被我的探针上限截断。
## 为什么这与我的上一个提交直接相关
`1ef1998` 把 401 分支的固定等待从 800ms 降到 30ms(修「认证过期时每个
请求白等 0.8s」)。但重试**没有次数上限** ⇒
- 改前:每 800ms 慢速空转
- 改后:每 30ms 快速烧 CPU + 反复打服务端
**我的优化把这个 bug 放大了。** 真机上探针调用 `api()` 直接挂死,
我起初还以为是我的测试写法问题。
## 修法
两处递归点(真 401 / 门户返回 200 但内容是登录页)都改成:
try {
return await api(p, o);
} finally {
window._haReloginLock = false;
}
`finally` 保证递归抛错时也释放锁 —— 否则会把后续所有请求都锁死成直接 401。
## 判据 retry-guard.test.mjs
在 `node:vm` 沙箱里跑**真实抽出的 `api()`**,用恒回 401 的 `fetch` 驱动,
统计真实调用次数。
### 写这条判据时踩的四个坑(都记在文件里)
1. **先给了假绿灯**:把 401 分支当独立函数体执行,但那块以
`return api(p,o)` 结尾、外面没有调用它的上下文 ⇒ 我从未真正进入那个
`if` ⇒ `api 递归=0` ⇒ 什么都没测到。改成跑**真实 api()** 才对。
2. **递归时忘了保持 `r.status===401`**:真实场景是「重登后凭据仍是错的」。
漏了它 ⇒ 递归那层不进 401 分支 ⇒ 又一次假绿灯。
3. **沙箱 `setTimeout` 只记录不执行**:`api()` 用它做超时控制
(`setTimeout(() => ctl.abort(), to)`)⇒ AbortController 永不被 abort
⇒ 表现为「fetch 只调 1 次、8000ms 被当成 401 等待」。
4. **沙箱缺 `clearTimeout`** ⇒ 抛 `clearTimeout is not defined` ⇒
整段在 fetch 之后就断了 ⇒ 永远走不到 401 分支。
★ 共同点:**沙箱不完整 ⇒ 静默地什么都没测 ⇒ 假绿灯**。
判据自己给假绿灯比没有判据更危险。
### 变异测试(精确锚点,验证判据真能抓)
把第一处 try/finally 退回「递归前清锁」⇒ 判据立刻红
(`fetch 被调 13 次`);恢复后全绿。
★ 第一次做这个变异时我误判「判据漏抓」—— 实际是我的变异脚本用了模糊
锚点、**压根没改到文件**(`grep` 显示 return await 从 2 变 1,但 `sed`
命中的是另一处)。两个信号矛盾时先坐实文件状态,别急着改判据。
## 顺带把 `make test` 的门禁修好(9c3d285 / f59a3cf 之外)
新判据已接入 `npm test`,实测 14 项通过、`make test-gui` 全绿。