Files
HomeAgent/internal/plugin/proc/evtpoll_unix.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

35 lines
975 B
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 unix
package proc
import (
"errors"
"golang.org/x/sys/unix"
)
// pollEvtfd 等待 fd 可读,最多 timeoutMs 毫秒。超时返回 (false, nil)。
//
// 为什么不用 os.File.SetReadDeadlineeventfd/pipe 经 os.NewFile 包装后
// **不会**注册进 Go netpolleros.NewFile 对非 open 得到的 fd 一律按非
// pollable 处理Read 退化成阻塞 syscallSetReadDeadline 返回错误且
// 不生效。实测表现是 Run 永久卡在 syscall.ReadStop 无法打断。
//
// 用 poll(2) 显式加超时,消费循环才能周期性回到 stop 检查。
func pollEvtfd(fd int, timeoutMs int) (bool, error) {
if fd < 0 {
return false, errors.New("evtfd: 非法 fd")
}
fds := []unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN}}
for {
n, err := unix.Poll(fds, timeoutMs)
if err == unix.EINTR {
continue // 被信号打断:重试,超时预算不变
}
if err != nil {
return false, err
}
return n > 0, nil
}
}