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

5

我有一个node.js进程,需要从多个命名管道中读取数据,这些管道由其他不同的进程作为IPC方法提供。

当我打开并创建了四个及以上的管道读取流之后,发现fs似乎无法再打开管道,并且停在那里不动。

考虑到可以同时打开数千个文件而不会出现问题(例如通过将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++中,

  • 脚本仅打开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 潜在的Node.js相关问题:经过一番搜索,似乎与Node.js Github上的这个问题有关:https://github.com/nodejs/node/issues/1941。但人们似乎抱怨相反的行为(fs.open()抛出EMFILE错误而不是静默挂起...)
正如您所看到的,我尝试在许多方向上进行搜索,所有这些都导致了我的问题:
您知道是什么原因导致这种行为吗?
谢谢

是的,我评论了这种行为,这可能表明它来自Node.js。Github上的问题表明他们可能在此期间实现了某种队列... - Sami
不,没关系,我尽可能详尽地尝试了一切,但我猜这可能会让长度有点压倒性 :| - Sami
这更多是为了保持评论区的整洁,这样后续的读者就不需要阅读无用的评论。但很高兴看到你能找到解决方案。 - t.niese
1个回答

6

我在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.
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接