fix(proc): EvtConsumer 生命周期——消除 host.Close 后的 SIGSEGV

预存缺陷(非本次重构引入,但会稳定复现崩溃):

Stop() 只 close 了 stop channel,而 Run() 阻塞在 evtfd.Read 里,
根本没有机会检查 stop。调用方在 Stop 后释放 ringData(host.Close
会 munmap 整个区域),Run 一旦从 Read 恢复就会读已解除映射的内存:
**SIGSEGV,recover 捕不到**。实测 TestEventRing_OverflowStillDelivers
约 50% 概率触发。

两次尝试与结论:
1. os.File.SetReadDeadline 无效——eventfd/pipe 经 os.NewFile 包装后
   **不会**注册进 Go netpoller(os.NewFile 对非 open 得到的 fd 一律按
   非 pollable 处理),Read 是阻塞 syscall,SetReadDeadline 返回错误。
2. 改为 poll(2) 显式加超时(evtpoll_unix.go),消费循环每 100ms 回到
   stop 检查。

新增 API 契约:
- Stop() 非阻塞,仅请求退出
- Wait() 阻塞至 Run 退出;**返回后才能释放 ringData**
- drainEvents 每条事件后检查 stop,避免慢 handler 拖延退出

其他:
- evtring_test.go 三个用例改为 defer Wait() → defer Stop()(LIFO 保证
  Wait 先于 host.Close 完成)
- 溢出用例的 handler 改为非阻塞投递:写入了 8292 条事件而 channel 只
  消费 1 条,阻塞投递会让 drainEvents 卡在 handler 里,Stop 无法退出
This commit is contained in:
JianFeeeee
2026-09-10 17:56:54 +08:00
parent ca584bda3f
commit c03de9878d
4 changed files with 111 additions and 6 deletions

View File

@ -34,6 +34,9 @@ func TestEventRing_BasicWriteAndConsume(t *testing.T) {
},
)
go consumer.Run()
// LIFO:先 Stop(打断阻塞的 Read)再 Wait(等 Run 退出),
// 两者都必须在 host.Close(munmap 整个区域)之前完成。
defer consumer.Wait()
defer consumer.Stop()
// 订阅 agent_output 事件
@ -85,11 +88,18 @@ func TestEventRing_OverflowStillDelivers(t *testing.T) {
host.EvtfdReadFile(),
0,
func(evt *pubsdk.Event) error {
received <- evt
// 非阻塞投递:本用例写入了 8292 条事件,若这里阻塞在
// channel 上,drainEvents 会卡在 handler 里,Stop 就无法
// 让 Run 退出。
select {
case received <- evt:
default:
}
return nil
},
)
go consumer.Run()
defer consumer.Wait()
defer consumer.Stop()
select {
@ -125,6 +135,7 @@ func TestEventRing_TypeMaskFiltering(t *testing.T) {
},
)
go consumer.Run()
defer consumer.Wait()
defer consumer.Stop()
unsub := er.Subscribe(pubsdk.EventToolCall)