我有一个相当复杂的NodeJS应用程序,它可以管理服务器上的各种操作。它在PubNub通道上侦听消息,并向第三方API发出多个请求。它还有一个计时器(使用setTimeout),该计时器每秒运行一次,以检查所有相关进程是否仍在运行并且运行正常。
最近我注意到一些奇怪的行为。在某些情况下(特定的,但100%可复制),它会进入一种奇怪的状态,即所有网络I / O都停止工作。 PubNub更新将停止接收,并且任何发出的HTTP请求(使用request-promise-native库)都将超时。一旦应用程序进入状态,此状态便会显示为永久状态,因为我尝试运行超时时间很长的请求,但它们永远无法解决。
我一直在线阅读有关如何通过使事件循环停止(通常在主线程上使用长时间运行的操作,process.nextTick或对Promise的用法不当)来使IO饿死的可能。我的应用程序中没有这些。
但是,即使很奇怪,我也读过,如果您阻塞事件循环,则所有计时器也会停止工作。我在应用程序内使用setTimeout,setInterval和setIntermediate,即使应用程序已进入此状态,它们都将继续工作。因此,我的事件循环显然仍在运行。
我认为问题很可能是由于我使用命名管道引起的。我的应用程序的一部分创建了一个命名管道,然后将其读取为流:
let pipePath = "/tmp/myPipe";
await promisify(fs.unlink)(pipePath)
.then(function() {}, function(err) {console.warn("Error removing pipe", err);}); // Ignore errors which may happen if the pipe doesn't exist
childProcess.spawnSync('mkfifo', [pipePath]);
// (not shown) here it starts a 3rd party application that writes to the pipe
let pipeHandle = await promisify(fs.open)(pipePath, O_RDWR); // https://stackoverflow.com/a/53492514/674675
let stream = fs.createReadStream(null, {fd: pipeHandle, autoClose: false});
// (not shown) here several event listeners are added to stream's data event
我还会检测第三方应用程序是否由于某种原因而终止,以及何时清理管道:
// (not-shown) here all the listeners on the stream are removed using removeListener
stream.close(); // This also seems to close the handle to the file
如果第3方应用程序在未写入管道的情况下被杀死,则IO处于冻结状态。但确实很奇怪,只有在第四次之后才进入冻结的IO状态。因此,我可以创建管道,启动第三方应用程序,读取管道并杀死应用程序,然后再将其写入任何数据3次,但是第4次将以某种方式破坏NodeJS。
我正在使用Node v8.10.0。有人知道发生了什么吗?
我尝试过的事情:(没有成功)
我不信任steam.close()
关闭文件句柄,所以我尝试了以下替代方法:
// (not-shown) here all the listeners on the stream are removed using removeListener
stream.pause();
await promisify(fs.close)(pipeHandle);
我已更新到Node v11.8.0。
我从使用fs
切换到graceful-fs
。
我弄清楚了如何将Chrome的DevTools连接到该进程,并发现该错误的确切时刻似乎是在创建第五个管道之前调用fs.unlink时。对于fs.unlink的承诺永远不会得到解决或拒绝。仍然不确定为什么会这样,但是至少这是进展。
我尝试用spawnSync("rm", [pipePath])
替换fs.unlink调用。这样可以使代码前进到调用之后,但是fs.open
操作失败。我还尝试运行fs.openSync
,但它冻结了整个程序。因此,似乎在第四个进程被杀死之后,该应用由于某种原因无法再执行IO,并且下一个fs
调用将无限期地阻塞。
我逐步执行了该程序,并在另一个屏幕中使用lsof
来检查过程中每个有趣部分的文件描述符。通常,在创建管道后,我的临时文件中就有一个文件描述符,我开始读取它,然后关闭流后,描述符就消失了。但是在第四次关闭流之后,描述符仍然存在。这始终是第四次。 4的意义是什么?我开始变得四肢恐惧。
试图将autoClose更改为true。
试图创建一个重现此问题的最小示例。没有完全重现该问题,但也许找到了相关的东西? Question here
使用lslocks
来查看进程是否正在等待锁定。 (不是)
切换为使用fifo-js处理管道。这解决了问题!但不幸的是,它会处理通过管道传输的二进制数据,因此它不可用。