Files
HomeAgent/internal/plugin/proc
JianFeeeee aaf833a687 fix(proc): 关闭时等 stage 收尾,修每次 shutdown 必现的 SIGSEGV
连续两次部署都被验证门拦下(「近 2 分钟出现 5 条 panic/fatal」)。
查出这不是新版本崩了,而是**旧进程关闭时**必崩 —— 每次 shutdown 都触发:

    fatal error: fault
    [signal SIGSEGV: segmentation violation ...]
    proc.(*Segment).descOffset
    proc.(*Segment).setDesc
    proc.(*Segment).writeLocal.func1   shmcodec.go:104
    proc.(*Segment).writeLocal
    proc.(*Segment).WriteAll
    proc.(*stageCoordinator).enter     host.go:310
    proc.(*Host).beginStage
    proc.(*coreHandler).runStage
    agent/core.(*StageHost).RunStage

## 根因:Close 只处理了「事件环」一路写者,漏了「stage 数据面」

Host.Close 里那段长注释把事件环写得非常清楚(订阅 handler 会
ring.WritePush 写共享内存,unmap 后必炸,而 recover 捕不到 runtime
fatal),也**确实**在 freeShm 前退订了。

但共享段有**两条独立写者**。beginStage 的首进者会持 stageMu 调
Segment.WriteAll 写同一块段 —— 它**不在事件环订阅里**,退订阅根本管不到它。
于是 Close 只等问题 A 就 freeShm,问题 B 继续写已 unmap 的内存。

## 修法:用 stageMu 作为「没有 stage 在写段」的判据

stageMu 的既有语义已经就是我们要等的东西(首进者取、最后离开者放,
见 beginStage/endStage 注释),不必再造一个会与它失去同步的并行状态。
`Close` 现在 unmap 前独占获取它。

另加 `closing atomic.Bool`:光「等正在进行的 stage」还不够 —— Close
释放 stageMu 之后仍可能有新 stage 进场(agent 收尾路径),那时它照写已
释放的段。beginStage 现在看到一个明确的错误,而不是撞 SIGSEGV。

## 为何这件事必须修,而不只是「已知缺陷」

它让部署验证门失效:门报的「5 条 panic/fatal」分不清是新版本坏了,
还是旧版本关闭时的既有噪声。我这两次都手工核对了 PID 才敢继续 ——
下一个部署的人不该被迫做这种考古。修掉之后,这个门才真的能拦回归。

## 验证(判据可杀变异,逐个实测)

- `TestHost_CloseWaitsForInflightStage`:stage 持锁时 Close 必须阻塞。
  删掉 `closing.Store` + `stageMu.Lock` → FAIL「未等 stage 收尾就返回」
- `TestHost_BeginStageAfterCloseRejected`:Close 后 beginStage 必须报错。
  把检查改成 `if false` → **测试进程真的 SIGSEGV 被杀**(与生产同栈)
- `go test -race ./internal/plugin/proc/` 通过
- 全仓 `go test ./... -count=1` → 48 包 ok / 0 FAIL
2026-10-08 12:46:02 +08:00
..