Node.js fs.open() 在尝试打开 4 个以上的命名管道 (FIFO) 后挂起



我有一个节点.js进程,需要从由不同的其他进程作为IPC方法馈送的多个命名管道中读取。

在从四个以上的 fifo 打开并创建读取流后,我意识到 fs 似乎不再能够打开 fifo,只是挂在那里。

考虑到可以同时打开数千个文件而不会遇到麻烦(例如,在以下脚本中将mkfifo替换为touch(,这个数字似乎有点低。

我在 MacOS 10.13 上使用 node.js v10.1.0 和 Ubuntu 16.04 上的 node.js v8.9.3 进行了测试,结果相同。


错误的脚本

以及显示此行为的脚本:

var fs = require("fs");
var net = require("net");
var child_process = require('child_process');
var uuid = function() {
for (var i = 0, str = ""; i < 32; i++) {
var number = Math.floor(Math.random() * 16);
str += number.toString(16);
}
return str;
}
function setupNamedPipe(cb) {
var id = uuid();
var fifoPath = "/tmp/tmpfifo/" + id;
child_process.exec("mkfifo " + fifoPath, function(error, stdout, stderr) {
if (error) {
return;
}
fs.open(fifoPath, 'r+', function(error, fd) {
if (error) {
return;
}
var stream = fs.createReadStream(null, {
fd
});
stream.on('data', function(data) {
console.log("FIFO data", data.toString());
});
stream.on("close", function(){
console.log("close");
});
stream.on("error", function(error){
console.log("error", error);
});
console.log("OK");
cb();
});
});
}
var i = 0;
function loop() {
++i;
console.log("Open ", i);
setupNamedPipe(loop);
}
child_process.exec("mkdir -p /tmp/tmpfifo/", function(error, stdout, stderr) {
if (error) {
return;
}
loop();
});

这个剧本在他身后不干净,别忘了rm -r /tmp/tmpfifo

Repl.it 链接


注意,这个问题的以下部分与我已经尝试回答的问题有关,但可能不是它的核心


这个脚本的两个有趣的事实

  • 当在其中一个FIFO中写入两次时,(即echo hello > fifo(节点能够再打开一个FIFO,但不再从我们写入的FIFO接收
  • 当通过直接提供FIFO(而不是FD(的路径来创建读取流时,脚本不再阻塞,但显然不再接收任何FIFO中写入的内容

调试信息

然后,我尝试验证这是否与某些操作系统限制有关,例如打开的文件描述符数量。

Mac上的ulimit -a输出是

core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
file size               (blocks, -f) unlimited
max locked memory       (kbytes, -l) unlimited
max memory size         (kbytes, -m) unlimited
open files                      (-n) 256
pipe size            (512 bytes, -p) 1
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 1418
virtual memory          (kbytes, -v) unlimited

没有什么指向 4 的某个限制。


暂定C++

然后我尝试用C++编写类似的脚本。 C++脚本成功打开了一百个fifo。

请注意,这两种实现之间存在一些差异。在C++,

  • 脚本仅打开 FIFO,
  • 没有阅读的试探,
  • 并且没有多线程

#include <string>
#include <cstring>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>
int main(int argc, char** argv)
{
for (int i=0; i < 100; i++){
std::string filePath = "/tmp/tmpfifo/" + std::to_string(i);
auto hehe = open(filePath.c_str(), O_RDWR);
std::cout << filePath << " " << hehe << std::endl;
}
return 0;
}

作为旁注,需要在执行脚本之前创建 fifo,例如使用

for i in $(seq 0 100); do mkfifo /tmp/tmpfifo/$i; done


潜在节点.js相关问题

经过一番搜索,它似乎也链接到 Node.js Github 上的该问题:

https://github.com/nodejs/node/issues/1941。

但是人们似乎在抱怨相反的行为(fs.open((抛出EMFILE错误并且没有静默挂起......


如您所见,我试图向多个方向搜索,所有这些都引出了我的问题:

您知道什么可能导致此行为吗?

谢谢

所以我在Node上问了这个问题.js Github,https://github.com/nodejs/node/issues/23220

从解决方案:

处理FIFO目前有点棘手。

默认情况下,open()系统调用 FIFO 上的块,直到管道的另一侧也打开。由于 Node.js 使用线程池进行文件系统操作,因此打开open()调用未完成的多个管道会耗尽此线程池。

解决方案是在非阻塞模式下打开文件,但这有一个困难,即其他fs调用的构建没有考虑到非阻塞文件描述符; 然而,net.Socket是。

因此,解决方案如下所示:

fs.open('path/to/fifo/', fs.constants.O_RDONLY | fs.constants.O_NONBLOCK, (err, fd) => {
// Handle err
const pipe = new net.Socket({ fd });
// Now `pipe` is a stream that can be used for reading from the FIFO.
});

最新更新