新博客链接:https://note.mogutou.xyz/articles/2019/08/15/1565876205121.html
Fast event-loop networking for Go
最近翻了 evio 的源码,发现一些问题,主要集中在 linux 平台 epoll 上和读写的处理。
- 用来唤醒 epoll 的 eventfd 写入数据没有读出
- listen 的 fd 注册到所有事件循环,epoll 的惊群问题
- loopWrite 在内核缓冲区满,无法一次写入时,出现写入数据丢失
eventfd 的使用问题
在 internal/internal_linux.go 中封装了 epoll 的使用 API 。
// Poll ...
type Poll struct {
fd int // epoll fd
wfd int // wake fd
notes noteQueue
}
在 OpenPoll 时,会创建一个 eventfd 并将 fd 赋值给 Poll 的 wfd 成员, 并且注册到 epoll 监听可读事件。
当需要唤醒当前 epoll 时,提供了 Trigger 方法
// Trigger ...
func (p *Poll) Trigger(note interface{
}) error {
p.notes.Add(note)
_, err := syscall.Write(p.wfd, []byte{
0, 0, 0, 0, 0, 0, 0, 1})
return err
}
这是往刚刚提到的 eventfd 中写入八字节数据,此时 epol l会被唤醒 epoll_wait 函数返回。 但是,evio 并没有去把 8 个字节的数据读取出来,内核缓冲区会不断积压,并且 evio 使用的是 epoll 的LT模式(默认模式),只要缓冲区中有数据,epoll 就会不断唤醒。这应该算是一个 bug 吧。
listen 的 fd 注册到所有事件循环,epoll 的惊群问题
evio 可以指定启动多个事件循环。evio 将 listen fd 注册到每一个事件循环中(epoll)监听可读事件,所以当一个连接到来时,所有的事件循环都会唤醒。
// create loops locally and bind the listeners.
for i := 0; i < numLoops; i++ {
l := &loop{
idx: i,
poll: internal.OpenPoll(),
packet: make([]byte, 0xFFFF),
fdconns: make(map[int