Node.js中关于Accept时Emfile的处理

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

[[403078]]

本文转载自微信公众号「编程杂技」,作者theanarkh。转载本文请联系编程杂技公众号。

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

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的时候会首先预留一个文件描述符。

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可能就会被用上。

// 摘取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。

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表示没有连接需要处理了,从而结束处理连接的整个逻辑。

 

责任编辑:武晓燕 来源: 编程杂技
相关推荐

2021-04-19 07:41:37

AcceptEmfile问题

2021-03-04 23:12:57

Node.js异步迭代器开发

2011-11-02 09:04:15

Node.js

2021-12-25 22:29:57

Node.js 微任务处理事件循环

2020-10-26 08:34:13

Node.jsCORS前端

2021-12-28 20:04:23

Node.js开发JavaScript

2023-10-04 07:35:03

2024-01-05 08:49:15

Node.js异步编程

2016-08-11 14:02:02

NodeJS前端

2021-05-21 09:36:42

开发技能代码

2020-04-15 15:48:03

Node.jsstream前端

2013-11-01 09:34:56

Node.js技术

2015-03-10 10:59:18

Node.js开发指南基础介绍

2017-03-19 16:40:28

漏洞Node.js内存泄漏

2017-03-20 13:43:51

Node.js内存泄漏

2020-05-29 15:33:28

Node.js框架JavaScript

2012-02-03 09:25:39

Node.js

2020-11-02 11:40:24

Node.jsRequire前端

2021-12-01 00:05:03

Js应用Ebpf

2011-09-08 13:53:31

Node.js
点赞
收藏

51CTO技术栈公众号