Files
HomeAgent/internal/agent
JianFeeeee 0c959e3ff3 fix(rtchild): pump 裸读写 data race —— 锁贯彻到底
## 缺陷(-race 实测)

rtChild 里 pump 是**唯一**"构造后被改写、又有并发读者"的字段,
但它的读写绕过了 rc.mu:

    Dispose(StopRTChild 的 goroutine)  rc.pump = nil      ← 写
    rtEmit (rtLoop 的 goroutine)        rc.pump          ← 读

-race 栈:

    Read at … core.(*Agent).rtEmit        rtchild.go:1275
    Previous write … core.(*rtChild).Dispose  rtchild.go:377

## 为什么之前没被发现

TestRTChild_E2E_Sustained 覆盖同一个窗口,但它是**偶发命中**:
全量负载下 5 次中 1 次,单跑 20 次 0 次,基线 af17af5 全量 0 次。
这种形状在 CI 里等于没有 —— 红了是运气,绿了不代表安全。

## 修法:锁贯彻到底(不是只补这一处)

rtChild 全部字段逐个判定过:

    字段              构造后写?        遵守锁规?
    pump              是(Dispose)      ★ 本提交修:读写都持 rc.mu
    light/mainRO      是(Dispose)      ✓ 无并发读者,只在 Dispose 内用
    slot/input/output/agent  否        ✓ 只读,不需要锁
    history           是(append)       ✓ 三处访问全部持锁(原有)
    round/lastSeq/curCancel/critical/suppressedCancels  ✓ 原本就持锁

即:pump 是唯一的漏网者,其余要么本已正确、要么根本没有并发写。

## 两个顺序要求(不只是"加个锁")

① **取走并置 nil 在锁内,Close 在锁外**

   pump.Close() 内部 wg.Wait() 等消费 goroutine 收尾;持锁等待可能与
   另一个持锁路径交叉。而"置 nil"必须与读者的"读"互斥,否则读者会
   拿到一个已被清掉的指针。

② **rtEmit 锁内取、锁外 Push**

   Push 在队列满时会阻塞,持锁会卡死 Dispose 的置 nil。

## 判据(2 条,2 次变异全部实测变红)

  TestRTPumpAccessIsLocked
      一边不停 emit、一边 Dispose,用 -race 判定。
      变异 A(rtEmit 裸读)  ⇒ 连跑 3 次全部命中 DATA RACE
      变异 B(Dispose 裸写) ⇒ 第 1 次命中 DATA RACE

  TestRTPumpAfterDisposeIsNil
      钉住顺序语义:Dispose 之后 rc.pump 必须为 nil(否则 emit 会推
      送到已关闭的 pump),且兜底路径不 panic。

## 判据第一版是假的(记下来)

第一版读者写的是 `for i := 0; i < 500; i++` —— 500 次 emit 微秒级跑完,
而写者 sleep 1ms 才动手,两者**根本不重叠**。变异 B(裸写)连跑 6 次
都不红。改成"持续跑到写者宣布结束"之后才真正抓到。

这正是「判据必须能变红」的意义:不跑变异就永远不知道它是真判据还是摆设。

## 判据的边界(诚实说明)

这条判据的"红"依赖 -race 检测器。不带 -race 时它只能发现 panic/死锁。
所以必须与 `go test -race` 一起跑 —— 提交纪律第 4 条已要求全量 -race,
这里只是把「哪个窗口最危险」钉成可复跑的形态。

## 验证(提交纪律四项全绿)

    go build ./...                    0
    go vet ./...                      0
    gofmt -l internal/ cmd/ pkg/      干净
    go test ./... -count=1 -race      0 竞态,62 包全过
    干净检出(worktree)              build + TestRTPump 全绿
2026-11-04 16:53:13 +08:00
..