Files
HomeAgent/internal/agent
JianFeeeee f33ebee01b test(agent/core): 消除测试对 scheduler.queue 的无锁直读,并加判据防回归
## 起因

offload_test.go 等 4 个文件里有 18 处 `len(s.queue)` / `range s.queue`,
全在锁外。当时**安全**——那些测试用 newRootWithoutSchedulerLoop,
刻意不启 schedulerLoop,所以没有并发写者。

但这个安全性 ① 不由测试自身保证,只由「别改构造函数」这个口头约定保证;
② 一旦有人换成 newRootWith 就变成真竞争,症状是「今天全绿、改一次就炸」。

先确认生产代码没问题:逐处核对 scheduler.go 里 6 个读 s.queue 的函数,
全部在 mu.Lock/RLock 内(takeQueuedInputs/hasRoom/nextRef/armStop/
pendingEvents/takeAllPendingEvents)。所以这是**测试的隐患**,不是核心缺陷。

## 改动

- scheduler.go 新增两个带锁只读访问器:
  · queueLen() —— 替代 len(s.queue)
  · queueSnapshot() —— 返回浅拷贝,替代 range/索引(拷贝保证 len 与
    range 不会在遍历中途变化)
- 4 个测试文件 18 处直读全部改走访问器
- 新增 queue_access_test.go:AST 静态判据,扫所有 *_test.go,
  同一函数内出现过 Lock/RLock 即视为持锁(Go 的 defer Unlock 静态难还原,
  按函数近似足够保守);命中无锁直读即红

## 判据自检

静态判据最大的失败模式是「根本没抓到东西」,所以带双向自检:

- 正向:注入一处无锁直读 → 必须被抓到
- 反向:注入一处持锁直读 → 必须**不**被报(否则噪声大没人看)
- 真变异:把一处访问器改回 `len(s.queue)` → 红

## 验证

- go build ./... / go vet / go test ./internal/... 全绿
- go test -race ./internal/agent/core/ 无数据竞争
- 部署后:39 插件加载、3 个 multimodal 工具注册、0 panic/fatal
- graph.db integrity ok,blocks 2787 未动

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-10-06 09:22:12 +08:00
..