Node.js fs.open() 在尝试打开超过 4 个命名管道 (FIFO) 后挂起
Node.js fs.open() hangs after trying to open more than 4 named pipes (FIFOs)
我有一个 node.js 进程需要从不同的其他进程提供的多个命名管道中读取作为 IPC 方法。
我在从超过四个 fifos 打开并创建读取流后意识到,fs 似乎不再能够打开 fifos,只是挂在那里。
考虑到可以同时打开数千个文件而不会出现问题(例如,在以下脚本中将 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
注意,此问题的以下部分与我已经尝试回答的问题有关,但可能不是它的核心内容
关于这个脚本的两个有趣的事实
- 当在其中一个 FIFO 中写入两次时,(即
echo hello > fifo
)节点可以再打开一个 fifo,但不再从我们写入的那个中接收数据
- 当通过直接提供到 fifo(而不是 fd)的路径创建读取流时,脚本不再阻塞,但显然不再接收任何 FIFO 中写入的内容
调试信息
然后我尝试验证这是否与某些 OS 限制有关,例如打开的文件描述符的数量。
ulimit -a
在 Mac 上的输出是
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++ 中,脚本成功打开了一百个 fifos。
请注意,这两种实现之间存在一些差异。在 C++ 中,
- 脚本只打开fifos,
- 没有试读,
- 并且没有多线程
#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;
}
作为旁注,需要在执行脚本之前创建 fifos,例如
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 错误而不是静静地挂起...)
如您所见,我尝试了多个方向的搜索,所有这些都引出了我的问题:
你知道什么会导致这种行为吗?
谢谢
所以我在 Node.js Github, https://github.com/nodejs/node/issues/23220
上问了问题
来自解决方案:
Dealing with FIFOs is currently a bit tricky.
The open()
system call blocks on FIFOs by default until the other side of the pipe has been opened as well. Because Node.js uses a threadpool for file-system operations, opening multiple pipes where the open()
calls don’t finish exhausts this threadpool.
The solution is to open the file in non-blocking mode, but that has the difficulty that the other fs
calls aren’t built with non-blocking file descriptors in mind; net.Socket
is, however.
So, the solution would look something like this:
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.
});
我有一个 node.js 进程需要从不同的其他进程提供的多个命名管道中读取作为 IPC 方法。
我在从超过四个 fifos 打开并创建读取流后意识到,fs 似乎不再能够打开 fifos,只是挂在那里。
考虑到可以同时打开数千个文件而不会出现问题(例如,在以下脚本中将 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
注意,此问题的以下部分与我已经尝试回答的问题有关,但可能不是它的核心内容
关于这个脚本的两个有趣的事实
- 当在其中一个 FIFO 中写入两次时,(即
echo hello > fifo
)节点可以再打开一个 fifo,但不再从我们写入的那个中接收数据 - 当通过直接提供到 fifo(而不是 fd)的路径创建读取流时,脚本不再阻塞,但显然不再接收任何 FIFO 中写入的内容
调试信息
然后我尝试验证这是否与某些 OS 限制有关,例如打开的文件描述符的数量。
ulimit -a
在 Mac 上的输出是
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++ 中,脚本成功打开了一百个 fifos。
请注意,这两种实现之间存在一些差异。在 C++ 中,
- 脚本只打开fifos,
- 没有试读,
- 并且没有多线程
#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;
}
作为旁注,需要在执行脚本之前创建 fifos,例如
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 错误而不是静静地挂起...)
如您所见,我尝试了多个方向的搜索,所有这些都引出了我的问题:
你知道什么会导致这种行为吗?
谢谢
所以我在 Node.js Github, https://github.com/nodejs/node/issues/23220
上问了问题来自解决方案:
Dealing with FIFOs is currently a bit tricky.
The
open()
system call blocks on FIFOs by default until the other side of the pipe has been opened as well. Because Node.js uses a threadpool for file-system operations, opening multiple pipes where theopen()
calls don’t finish exhausts this threadpool.The solution is to open the file in non-blocking mode, but that has the difficulty that the other
fs
calls aren’t built with non-blocking file descriptors in mind;net.Socket
is, however.So, the solution would look something like this:
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. });