-
-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
出现大量的 <Error: failed to delete fd=xxx from poller in event-loop(0): epoll_ctl del: no such file or directory> #398
Comments
这个错误是连接关闭时把 socket fd 从 epoll 里移除时出错,提示 fd 不存在。 |
能说明一下具体是在什么情况下发生的吗?服务当时在做什么,如果有相关的代码更好。 |
我尽量复现一下。 首先我服务启动后有一个后台定时任务,触发某个条件后,会调用 Conn.Close()。 但是此时已经通过 AsyncWrite 提交了相当多的任务在 Poller 的异步队列中,engine 没有结束,所以一直从队列中取出并 asyncWrite: // connection.go:434
func (c *conn) AsyncWrite(buf []byte, callback AsyncCallback) error {
...
return c.loop.poller.Trigger(c.asyncWrite, &asyncWriteHook{callback, buf})
} 由于conn被关闭,write 的时候出错,会走到 closeConn: ...
var sent int
if sent, err = unix.Write(c.fd, data); err != nil {
// A temporary error occurs, append the data to outbound buffer, writing it back to the peer in the next round.
if err == unix.EAGAIN {
_, _ = c.outboundBuffer.Write(data)
err = c.loop.poller.ModReadWrite(c.pollAttachment)
return
}
return -1, c.loop.closeConn(c, os.NewSyscallError("write", err))
}
... closeConn 时,会调用 // eventloop.go:177
...
err0, err1 := el.poller.Delete(c.fd), unix.Close(c.fd)
if err0 != nil {
rerr = fmt.Errorf("failed to delete fd=%d from poller in event-loop(%d): %v", c.fd, el.idx, err0)
}
if err1 != nil {
err1 = fmt.Errorf("failed to close fd=%d in event-loop(%d): %v", c.fd, el.idx, os.NewSyscallError("close", err1))
if rerr != nil {
rerr = errors.New(rerr.Error() + " & " + err1.Error())
} else {
rerr = err1
}
}
... 这也是本 issue 标题中的错误大量出现的原因。 一句话描述:write 失败就去 closeConn,恰巧 Poller 中的队列中任务非常多,重复 closeConn 导致。 疑问点:
func (c *conn) asyncWrite(itf interface{}) (err error) {
if !c.opened {
return nil
}
hook := itf.(*asyncWriteHook)
_, err = c.write(hook.data)
if hook.callback != nil {
_ = hook.callback(c) // 这个回调要是能加上 error 就好了
}
return
}
// internal/netpoll/epoll_default_poller.go:164
for i := 0; i < MaxAsyncTasksAtOneTime; i++ {
if task = p.asyncTaskQueue.Dequeue(); task == nil {
break
}
switch err = task.Run(task.Arg); err {
case nil:
case errors.ErrEngineShutdown:
return err
default:
logging.Warnf("error occurs in user-defined function, %v", err)
}
queue.PutTask(task)
}
|
这个不是你前面说的触发了服务端的某个条件被主动关闭了吗?那这里 client 的连接也是会收到断开的通知啊! |
不是的,我的服务是一个 client,主动关闭也是 client 的逻辑。client 与多个下游建立了长连接,每条连接都有定时探活的逻辑,探活失败会主动关闭当条连接。但是我理解关闭当条连接导致大量的上述错误打印,不应该影响其他连接,这是我疑惑的点。 |
关于造成第二点(Client 上所有连接都断了)的原因,昨天找到了——我们基础设施有实例自动迁移,大量的错误触发了自动迁移条件,原有实例被 kill 后又重新拉起,这个场景没有 panic,有报警但是被其他同学关掉了。 |
简单总结一下:标题不是 gnet 的 bug,是使用过程中 对于 gnet,个人觉得还是有以下可优化的点:
|
我觉得可能得修改下 AsyncCallback 的定义,多接收一个 error 作为入参,不过这样会导致前面的版本有代码不兼容;也可以保持兼容,修改成 |
为了不破坏兼容性,可以多定义一个 callback type AsyncCallbackWithError func(c Conn, err error) error 在 func (c *conn) asyncWrite(itf interface{}) (err error) {
if !c.opened {
return nil
}
hook := itf.(*asyncWriteHook)
_, err = c.write(hook.data)
if hook.callback != nil {
_ = hook.callback(c)
}
if hook.callbackWithErr != nil && err != nil {
_ = hook.callbackWithErr(c, err)
}
return
} |
这样的话, |
这个你有兴趣搞吗?如果没时间我这两天就自己提交 commit 了。 |
Related issue: #328 |
我看了下connection.go里conn的定义,是无锁的,而且Close也是没有判断opend状态直接就trigger的。保持这样的话我估计是无法解决类似问题的,原因我好像之前在其他一些issue里讲到过,但是搜不到了。 |
在我的观念里,无锁应该只适合少量场景下队列的push/pop操作来提高性能。尤其是对于conn这种单个fd上的并发竞争其实很少,这样的话锁也只剩下if+原子操作,并没有太大成本浪费。 |
github issue的搜索也是有点迷了 |
Note: this is a breaking change, the existing codebase using AsyncCallback must be updated accordingly. Fixes panjf2000#398
Describe the bug
服务突然出现大量的此类错误,然后整个服务不可用。
想问下什么场景会出现此类错误
Expected behavior
A clear and concise description of what you expected to happen.
Screenshots
If applicable, add screenshots to help explain your problem.
System Info (please fill out the following information):
The text was updated successfully, but these errors were encountered: