Files
HomeAgent/internal/plugin/evtring_test.go
JianFeeeee c03de9878d 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 无法退出
2026-09-10 17:56:54 +08:00

172 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//go:build linux || darwin
package plugin
import (
"testing"
"time"
"gitcode.com/JianFeeeee/HomeAgent/internal/events"
"gitcode.com/JianFeeeee/HomeAgent/internal/plugin/proc"
pubsdk "gitcode.com/JianFeeeee/homeagent-sdk/sdk"
)
// 事件环基础测试Host 创建事件环 → EventRing 写入 → 消费者读到。
func TestEventRing_BasicWriteAndConsume(t *testing.T) {
host, err := proc.NewHost()
if err != nil {
t.Fatalf("NewHost: %v", err)
}
defer host.Close()
bus := events.NewBus()
er := NewEventRing(host.EvtRing(), int(host.Evtfd().Fd()), bus)
// 消费者:从事件环段读取事件
received := make(chan *pubsdk.Event, 10)
consumer := proc.NewEvtConsumer(
host.EvtData(),
host.EvtfdReadFile(),
0, // typeMask = 0接收全部事件
func(evt *pubsdk.Event) error {
received <- evt
return nil
},
)
go consumer.Run()
// LIFO先 Stop打断阻塞的 Read再 Wait等 Run 退出),
// 两者都必须在 host.Closemunmap 整个区域)之前完成。
defer consumer.Wait()
defer consumer.Stop()
// 订阅 agent_output 事件
unsub := er.Subscribe(pubsdk.EventAgentOutput)
defer unsub()
// 发布事件
bus.Publish(&events.Event{
Type: events.EventAgentOutput,
Payload: map[string]interface{}{"text": "hello"},
})
// 等待消费者读到
select {
case evt := <-received:
if evt.Type != pubsdk.EventType(events.EventAgentOutput) {
t.Errorf("事件类型 = %v期望 %v", evt.Type, events.EventAgentOutput)
}
case <-time.After(2 * time.Second):
t.Error("消费者在 2s 内未收到事件")
}
}
// 事件环溢出测试:写入超过 cap 时消费者仍能读到最新事件。
func TestEventRing_OverflowStillDelivers(t *testing.T) {
host, err := proc.NewHost()
if err != nil {
t.Fatalf("NewHost: %v", err)
}
defer host.Close()
bus := events.NewBus()
er := NewEventRing(host.EvtRing(), int(host.Evtfd().Fd()), bus)
// 不启动消费者,直接写入超过 cap 的事件(需先订阅,否则 Bus 不会触发事件环写入)
unsub := er.Subscribe(pubsdk.EventSystem)
defer unsub()
for i := uint32(0); i < 8192+100; i++ {
bus.Publish(&events.Event{
Type: events.EventSystem,
Payload: map[string]interface{}{"seq": i},
})
}
// 启动消费者,应能读到最新事件
received := make(chan *pubsdk.Event, 10)
consumer := proc.NewEvtConsumer(
host.EvtData(),
host.EvtfdReadFile(),
0,
func(evt *pubsdk.Event) error {
// 非阻塞投递:本用例写入了 8292 条事件,若这里阻塞在
// channel 上drainEvents 会卡在 handler 里Stop 就无法
// 让 Run 退出。
select {
case received <- evt:
default:
}
return nil
},
)
go consumer.Run()
defer consumer.Wait()
defer consumer.Stop()
select {
case evt := <-received:
if evt == nil {
t.Error("收到 nil 事件")
}
case <-time.After(2 * time.Second):
t.Error("溢出后消费者在 2s 内未收到事件")
}
}
// typeMask 过滤测试:订阅者只收到匹配类型的事件。
func TestEventRing_TypeMaskFiltering(t *testing.T) {
host, err := proc.NewHost()
if err != nil {
t.Fatalf("NewHost: %v", err)
}
defer host.Close()
bus := events.NewBus()
er := NewEventRing(host.EvtRing(), int(host.Evtfd().Fd()), bus)
received := make(chan *pubsdk.Event, 10)
// typeMask 只订阅 tool_callbit 3 = 8
consumer := proc.NewEvtConsumer(
host.EvtData(),
host.EvtfdReadFile(),
1<<3, // tool_call
func(evt *pubsdk.Event) error {
received <- evt
return nil
},
)
go consumer.Run()
defer consumer.Wait()
defer consumer.Stop()
unsub := er.Subscribe(pubsdk.EventToolCall)
defer unsub()
// 发一个 tool_call 和一个 agent_output
bus.Publish(&events.Event{
Type: events.EventToolCall,
Payload: map[string]interface{}{"tool": "test"},
})
bus.Publish(&events.Event{
Type: events.EventAgentOutput,
Payload: map[string]interface{}{"text": "should be filtered"},
})
// 只应收到 tool_call
select {
case evt := <-received:
if evt.Type != pubsdk.EventType(events.EventToolCall) {
t.Errorf("收到错误类型 %v期望 tool_call", evt.Type)
}
case <-time.After(2 * time.Second):
t.Error("消费者在 2s 内未收到 tool_call 事件")
}
// agent_output 不应到达
select {
case evt := <-received:
t.Errorf("不应收到 agent_output实际收到 %v", evt)
case <-time.After(200 * time.Millisecond):
// 正确agent_output 被过滤
}
}