javascript - Node.js fs.open() 在尝试打开超过 4 个命名管道 (FIFO) 后挂起
我有一个 node.js 进程,需要从多个由不同其他进程提供的命名管道中读取,作为一种进程间通信方法。
我在打开并从四个以上的 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),Node 就能够再打开一个 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++ 的实现中,
- 脚本只打开了管道,
- 没有阅读的试探性安排,
- 并且不支持多线程
#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潜在的 Node.js 相关问题
经过一番搜索,这似乎也与 Node.js 的 Github 上的那个问题有关:
https://github.com/nodejs/node/issues/1941.
但是人们似乎在抱怨相反的行为(fs.open() 抛出 EMFILE 错误而不是静默挂起...)
正如你所看到的,我尝试了许多方向的搜索,而这一切都把我引向了我的问题:
你知道什么可能导致这种行为吗?
谢谢
答案#1
所以我在 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. });
