小记 Node.js 关于文件描述符的坑

14 天前
 zy445566

在之前遇到过一个 Node.js 使用文件描述符来读取文件,未释放文件描述符的坑,对此还对 Node.js 提过 PR ,让 Node.js 支持文件句柄的变量 GC 后,也同时销毁句柄,在 fs 的 FileHandleAPI 提供了未使用变量时进行尝试关闭文件描述符并提供警告,但是很多人并没有使用 FileHandleAPI ,而是更习惯于使用早期的 File System 的 API ,而除了显式的未释放的文件描述符,还有隐式的文件描述符。下面我则根据两个案例来进行讲解关于文件描述符的坑。

显式的文件描述符

显式的文件描述符,我们通常会使用 fs.open() 方法来打开文件,然后通过 fs.read() 方法来读取文件。如下代码所示。

const fs = require("fs");
fs.open("test.txt", "r", (err, fd) => {
  // 做一些操作
});
setInterval(() => {
  // 一直循环
}, 1000);

在这里其实有两个误区:

针对误区 1 ,因为大多数人在没有完整阅读 Node.js 文档,由于 Node.js 又存在回收机制,所以很多人会认为 fd 变量被回收了,那么 fd 对应的文件描述符也会被自动回收。

针对误区 2 ,这个误区则是没有 C 语言基础的同学容易犯的错误,而是更习惯于 JavaScript 的用法,更少的考虑回收问题,所以不会去 close 。

针对上面的误区,如果没有 close 的情况,什么时候文件描述符会被回收呢?在这个情况下,只有 Node.js 进程销毁的时候才会进行文件描述符的回收。

隐式文件描述符

对于显式的文件描述符,隐式的文件描述符更具有欺骗性,那么什么叫隐式的文件描述符呢?简单来说就是没有直接强制我们传递文件描述符或直接使用文件描述符却又使用到了文件描述符的场景,比如下面的代码。

const fs = require("fs");
// 创建一个文件读流
fs.createReadStream("test.txt");
setInterval(() => {
  // 一直循环
}, 1000);

在这里由于没有像显式使用文件描述符,而是将文件描述符作为可选变量,像这种隐式使用文件描述符的方法更具有欺骗性。

在上面的案例中,相信很多人都有类似的使用可读流的用法并且没有关闭可读流,但哪怕你仅仅式创建了可读流都将会生成一个文件描述符,和上面的显示文件描述符一样,必须手动关闭才能释放文件描述符,并且着这个文件描述符的占用都是 Node.js 进程销毁的时候才会进行回收。在这种场景下,如果你和 C 或 C++交互,哪怕你使用的是读流,都可能导致当前的文件描述符占用,导致 C 或 C++无法正常读取文件。

现象和解决方法

如果说我们的文件描述符被大量泄漏了,那么到了一定的数值,整个 Node.js 进程服务将会出现一个假死的现象,比如一直卡住在读文件的方法上,无法进行下一步运行,那么这个时候我们就可以考虑是否泄漏了文件描述符。

对于文件描述符的泄漏,我们在 linux 下可以使用lsof命令来查看你某个进程下的文件描述符情况。比如我们的 Node.js 进程是 12345 ,那么我们可以使用以下的命令来查看该进程下的文件描述符使用情况。

lsof -p 12345

然后我们根据文件的使用情况,查找代码的使用情况来对代码进行一个修改对文件描述符进行关闭,比如上面 fs.open 和 createReadStream 的例子,可以使用下面的代码来进行关闭。

const fs = require("fs");
fs.open("test.txt", "r", (err, fd) => {
  // 使用完成后关闭文件描述符
  fd.close();
});
const fd = fs.createReadStream("test.txt");
// 流使用完成后进行销毁
fd.destroy();
2106 次点击
所在节点    Node.js
25 条回复
mioktiar56
13 天前
不用在 Node.js 所定义的概念里面绕,都是对系统 API 的封装,了解系统 API 的使用就可以自然而然的规避这些问题。
实际上 fd 在 Windows 上就是句柄 HANDLE ,Linux 上是文件描述符。打开文件都需要显式关闭,如 CloseHandle 或 close
beiranc
13 天前
@nomagick 根据 OP 的 Github 找了一下应该是这个 https://github.com/nodejs/node/pull/35412
nomagick
13 天前
@beiranc 昨天就找出来了他自己 close 掉了不用再说什么了


@ysc3839
@sujin190
操作系统的 fd 是一个 int 型
beiranc
13 天前
@nomagick 不好意思没注意到评论的时间。。
Belmode
12 天前
开发这么多年来,我一只谨记,所有系统资源用完必定要释放,有 open 就必定要 close ,除非明确知道某些资源不用手动 close 。

所以说 nodejs 中这个情况是个坑,我很不认同。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1050068

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX