mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
预存缺陷(非本次重构引入,但会稳定复现崩溃): 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 无法退出
35 lines
975 B
Go
35 lines
975 B
Go
//go:build unix
|
||
|
||
package proc
|
||
|
||
import (
|
||
"errors"
|
||
|
||
"golang.org/x/sys/unix"
|
||
)
|
||
|
||
// pollEvtfd 等待 fd 可读,最多 timeoutMs 毫秒。超时返回 (false, nil)。
|
||
//
|
||
// 为什么不用 os.File.SetReadDeadline:eventfd/pipe 经 os.NewFile 包装后
|
||
// **不会**注册进 Go netpoller(os.NewFile 对非 open 得到的 fd 一律按非
|
||
// pollable 处理),Read 退化成阻塞 syscall,SetReadDeadline 返回错误且
|
||
// 不生效。实测表现是 Run 永久卡在 syscall.Read,Stop 无法打断。
|
||
//
|
||
// 用 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
|
||
}
|
||
}
|