3

我们有一个使用 epoll 来监听和处理 http-connections 的应用程序。有时 epoll_wait() 在“行”中两次接收 fd 上的关闭事件。含义:epoll_wait() 返回连接 fd,其中 read()/recv() 返回 0。这是一个问题,因为我将 malloc:ed 指针保存在 epoll_event 结构 (struct epoll_event.data.ptr) 中,并且在 fd 时释放(socket) 第一次被检测为关闭。第二次崩溃了。

这个问题在实际使用中很少发生(除了一个站点,实际上每台服务器大约有 500-1000 个用户)。我可以使用每秒超过 1000 个同时连接的 http siege 来复制问题。在这种情况下,应用程序段错误(由于指针无效)非常随机,有时在几秒钟后,通常在数十分钟后。我已经能够以每秒更少的连接数来复制该问题,但为此我必须运行该应用程序很长时间,很多天,甚至几周。

所有新的 accept() 连接 fd:s 都设置为非阻塞,并以一次性、边缘触发和等待 read() 可用的方式添加到 epoll。那么为什么当服务器负载很高时,epoll 认为我的应用程序没有获得关闭事件并排队新的?

epoll_wait() 在它自己的线程中运行,并将 fd 事件排队等待其他地方处理。我注意到有多个关闭传入简单代码,检查是否有从 epoll 到同一个 fd 的连续两次事件。它确实发生了,并且两者都关闭的事件(recv(..,MSG_PEEK)告诉我这个:))。

epoll fd 被创建:

epoll_create(1024);

epoll_wait() 运行如下:

epoll_wait(epoll_fd, 事件, 256, 300);

在 accept() 之后将 new fd 设置为非阻塞:

int flags = fcntl(fd, F_GETFL, 0);
错误 = fcntl(fd, F_SETFL, 标志 | O_NONBLOCK);

新的 fd 被添加到 epoll(客户端是 malloc:ed struct 指针):

静态结构 epoll_event ev;
ev.events = EPOLLIN | EPOLLONESHOT | 埃波莱特;
ev.data.ptr = 客户端;
err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client->fd, &ev);

并且在接收和处理来自 fd 的数据后,它会重新武装(当然是从 EPOLLONESHOT 开始)。起初我没有使用边缘触发和非阻塞 io,但我对其进行了测试并使用它们获得了不错的性能提升。这个问题在添加它们之前就存在了。顺便提一句。shutdown(fd, SHUT_RDWR) 在其他线程上用于触发适当的关闭事件,以便在服务器由于某些 http 错误等需要关闭 fd 时通过 epoll 接收(我实际上不知道这是否是正确的方法这样做,但效果很好)。

4

5 回答 5

4

一旦第一个 read() 返回 0,这意味着连接已被对等方关闭。为什么内核会为这种情况生成一个 EPOLLIN 事件?好吧,当您只订阅 EPOLLIN 时,没有其他方法可以指示套接字的关闭。您可以添加 EPOLLRDHUP,这与检查 read() 返回 0 基本相同。但是,请确保在测试 EPOLLIN之前测试此标志。

  if (flag & EPOLLRDHUP) {
     /* Connection was closed. */
     deleteConnectionData(...);
     close(fd); /* Will unregister yourself from epoll. */
     return;
  }

  if (flag & EPOLLIN) {
    readData(...);
  }

  if (flag & EPOLLOUT) {
    writeData(...);
  }

我订购这些块的方式是相关的,并且 EPOLLRDHUP 的返回也很重要,因为 deleteConnectionData() 可能已经破坏了内部结构。由于在关闭的情况下也设置了 EPOLLIN,这可能会导致一些问题。忽略 EPOLLIN 是安全的,因为它无论如何都不会产生任何数据。EPOLLOUT 也一样,因为它从未与 EPOLLRDHUP 一起发送!

于 2011-07-20T13:02:59.053 回答
1

epoll_wait() 在它自己的线程中运行,并将 fd 事件排队等待其他地方处理。... 那么为什么当服务器负载很高时,epoll 认为我的应用程序没有获得关闭事件并排队新的?

假设 EPOLLONESHOT 没有错误(尽管我没有搜索相关的错误),您正在另一个线程中处理您的 epoll 事件并且它偶尔崩溃或在高负载下崩溃的事实可能意味着您的某处存在竞争条件应用。

当您的服务器主动关闭客户端连接时,可能是 epoll_event.data.ptr 指向的对象在 epoll 事件在另一个线程中注销之前过早地被释放。

我的第一次尝试是在 valgrind 下运行它,看看它是否报告任何错误。

于 2011-01-18T13:05:24.527 回答
0

为远程主机关闭连接注册信号 0x2000 ex ev.events = EPOLLIN | EPOLLONESHOT | EPOLLET | 0x2000 并检查远程主机是否关闭连接(标志和 0x2000)

于 2013-12-30T12:55:02.473 回答
0

删除 EPOLLONESHOT 使问题在其他一些更改后消失。不幸的是,我不完全确定是什么原因造成的。将 EPOLLONESHOT 与线程一起使用并再次手动将 fd 添加到 epoll 队列中肯定是问题所在。epoll 结构中的数据指针也会在延迟后释放。现在完美运行。

于 2011-02-01T15:43:50.037 回答
0

我会根据以下部分重新检查自己epoll(7)

Q6
关闭文件描述符会导致它自动从所有 epoll 集中删除吗?

o 如果使用事件缓存...

那里有一些好处。

于 2011-01-18T20:06:13.563 回答