前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >Node.js中关于accept时EMFILE的处理

Node.js中关于accept时EMFILE的处理

作者头像
theanarkh
发布2021-07-08 16:03:02
发布2021-07-08 16:03:02
93600
代码可运行
举报
文章被收录于专栏:原创分享原创分享
运行总次数:0
代码可运行

EMFILE表示进程打开的文件描述符达到了上限,比如建立了一个TCP连接后,调用accept函数的时候就可能触发这个错误。那么这个会导致什么问题呢?首先我们看看Node.js是如何处理连接的。

代码语言:javascript
代码运行次数:0
复制
void uv__server_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
  uv_stream_t* stream;
  int err;

  stream = container_of(w, uv_stream_t, io_watcher);
  while (uv__stream_fd(stream) != -1) {
    // 摘取一个TCP连接
    err = uv__accept(uv__stream_fd(stream));
    // 记录下来
    stream->accepted_fd = err;
    // 执行上层回调,回调里消费accepted_fd
    stream->connection_cb(stream, 0);
    // 下一个循环
  }
}

当监听socket上可读事件触发的时候,Node.js就会执行uv__server_io进行处理。在uv__server_io中Node.js就会不断地调用accept摘取连接,然后执行回调处理该连接。这是正常的流程,那么如果accept出错了,那会怎么样?比如返回了EMFILE错误。

因为Node.js中,epoll的工作模式是水平触发,所以每轮事件循环中,uv__server_io都会被触发,然后执行accept,接着触发错误(如果还没有可用的文件描述符的话)。然而底层已完成三次握手的TCP连接无法得到处理,客户端也只能默默地在等待。Node.js选择的处理策略是关闭连接来通知客户端,服务器已经过载。我们看看Node.js具体是怎么做的。在初始化第一个Libuv stream的时候会首先预留一个文件描述符。

代码语言:javascript
代码运行次数:0
复制
 if (loop->emfile_fd == -1) {
    err = uv__open_cloexec("/dev/null", O_RDONLY);
    if (err < 0)
        /* In the rare case that "/dev/null" isn't mounted open "/"
         * instead.
         */
        err = uv__open_cloexec("/", O_RDONLY);
    if (err >= 0)
      loop->emfile_fd = err;
  }

我们看到Node.js打开了一个资源,然后拿到了一个文件描述符保存到emfile_fd。当Node.js处理TCP连接的时候,这个emfile_fd可能就会被用上。

代码语言:javascript
代码运行次数:0
复制
    // 摘取TCP连接
    err = uv__accept(uv__stream_fd(stream));
    if (err < 0) {
      // 文件描述符过载
      if (err == UV_EMFILE || err == UV_ENFILE) {
        err = uv__emfile_trick(loop, uv__stream_fd(stream));
        if (err == UV_EAGAIN || err == UV__ERR(EWOULDBLOCK))
          break;
      }

      stream->connection_cb(stream, err);
      continue;
    }

我们看到当uv_accept返回UV_EMFILE错误的时候,会执行uv__emfile_trick。

代码语言:javascript
代码运行次数:0
复制
static int uv__emfile_trick(uv_loop_t* loop, int accept_fd) {
  int err;
  int emfile_fd;

  if (loop->emfile_fd == -1)
    return UV_EMFILE;
  // 关闭预留的文件描述符,下面的uv_accept才能执行成果
  uv__close(loop->emfile_fd);
  loop->emfile_fd = -1;
  // 循环关闭无法处理的TCP连接
  do {
    // 摘取TCP连接
    err = uv__accept(accept_fd);
    if (err >= 0)
      // 关闭TCP连接,通知客户端服务器过载
      uv__close(err);
  } while (err >= 0 || err == UV_EINTR);
  // 重新获取一个预留的文件描述符
  emfile_fd = uv__open_cloexec("/", O_RDONLY);
  if (emfile_fd >= 0)
    loop->emfile_fd = emfile_fd;

  return err;
}

我们看到uv__emfile_trick中关闭了所有无法处理的TCP连接,然后重新补充预留的文件描述符。正常来说uv_accept最后会返回UV_EAGAIN表示没有连接需要处理了,从而结束处理连接的整个逻辑。

参考文章:如何优雅地处理 accept 出现 EMFILE 的问题

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档