EMFILE indicates that the file descriptor opened by the process reaches the upper limit. For example, when a TCP connection is established, the accept function may trigger the error. So what problems will this cause? First, let's look at node How JS handles connections.
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) { // Extract a TCP connection err = uv__accept(uv__stream_fd(stream)); // Record stream->accepted_fd = err; // Execute the upper layer callback and consume accepted in the callback_ fd stream->connection_cb(stream, 0); // Next cycle } }
When the readable event on the listening socket is triggered, the node JS will execute uv__server_io for processing. In UV__ server_ Node in io JS will constantly call accept to pick up the connection, and then execute a callback to process the connection. This is a normal process. What happens if accept makes an error? For example, EMFILE error is returned.
Because node In JS, the working mode of epoll is triggered horizontally, so in each round of event cycle, uv__server_io will be triggered, and then execute accept, and then trigger an error (if there is no available file descriptor). However, the TCP connection that has completed three handshakes at the bottom cannot be processed, and the client can only wait silently. Node. The processing strategy selected by JS is to close the connection to notify the client that the server has been overloaded. Let's look at node JS how to do it. When initializing the first Libuv stream, a file descriptor will be reserved first.
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; }
We see node JS opens a resource, then gets a file descriptor and saves it to emfile_fd. When node JS when handling TCP connections, this emfile_fd may be used.
// Extract TCP connection err = uv__accept(uv__stream_fd(stream)); if (err < 0) { // File descriptor overload 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; }
We see when uv_accept return UVs_ When emfile is wrong, UV will be executed__ 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; // Close the reserved file descriptor, UV below_ Accept to execute results uv__close(loop->emfile_fd); loop->emfile_fd = -1; // Loop closes TCP connections that cannot be processed do { // Extract TCP connection err = uv__accept(accept_fd); if (err >= 0) // Close the TCP connection and notify the client that the server is overloaded uv__close(err); } while (err >= 0 || err == UV_EINTR); // Retrieve a reserved file descriptor emfile_fd = uv__open_cloexec("/", O_RDONLY); if (emfile_fd >= 0) loop->emfile_fd = emfile_fd; return err; }
We see UV__ emfile_ Close all TCP connections that cannot be processed in trick, and then replenish the reserved file descriptor. Normally uv_accept finally returns the UVs_ Eagain indicates that no connection needs to be processed, thus ending the whole logic of processing the connection.
Reference article: How to gracefully handle the problem of EMFILE in accept