【发布时间】:2021-10-21 02:02:27
【问题描述】:
上下文:
我有一些代码大致如下:
const express = require("express");
const app = express();
const fetch = require('node-fetch');
app.get("/file/:path", async function(request, response) {
const path = request.params.path;
const reqController = new AbortController();
const reqTimeout = setTimeout(() => reqController.abort(), 10000);
const r = await fetch(`https://cdn.example.com/${encodeURIComponent(path)}`, {
timeout:10000,
signal: reqController.signal,
}).catch(e => false).finally(() => clearTimeout(reqTimeout));
if(r === false || !r.ok) {
response.send("error");
} else {
r.body.pipe(response);
}
});
...
所以您可以看到,我只是使用node-fetch 获取响应,然后将其传送到客户端响应流。
请注意,在实际代码中,我对错误的处理比.catch(e => false) 更好。很少有错误(几个月没有错误),所以我想我会省略这些细节。
问题:
此代码泄漏 TCP 连接或内存的可能方式有哪些?我如何“覆盖”这些案例?
额外信息:
我原以为默认情况下node-fetch/express 会有默认的流式超时(基于自收到最后一个块以来的时间),以防止泄漏,但情况似乎并非如此。
我试过添加这样的代码:
r.body.on('error', (error) => {
response.connection.destroy();
});
response.on('error', (error) => {
r.body.cancel();
});
r.body.pipe(response);
我对 node-fetch 和 Node.js 的流的内部没有很好的理解,所以这只是对可能涵盖某种异常故障模式的猜测,但事实证明这并没有解决问题。
请注意,我使用的是node-fetch 的timeout 选项(它是浏览器fetch 中不可用的特殊扩展名)。但是我刚刚编辑了第一个代码块,以表明我也在真实/未简化的代码中使用了中止信号。我最初添加了中止信号,因为我认为timeout 选项可能存在错误,但我仍然看到连接泄漏。现在仔细研究一下,我发现timeout 选项重置了重定向的超时时间,所以这可能是连接泄漏的一个有趣原因(继续无限重定向?但我猜无论如何都有重定向限制),但是唉,这不是我在这里遇到的问题,因为我也收到了中止信号。我正在使用 node-fetch v2.6.1 和 Node.js v14.7.0。
在应用程序/服务器重启几分钟后,知道这就是我在 netstat -an 和 cat /proc/net/sockstat 上看到的内容,这也可能会有所帮助:
{
LISTEN: 37,
TIME_WAIT: 4644,
ESTABLISHED: 268,
CLOSE_WAIT: 1,
SYN_SENT: 1,
SYN_RECV: 4,
FIN_WAIT1: 3
}
sockets: used 801
TCP: inuse 94 orphan 4 tw 4642 alloc 312 mem 5305
UDP: inuse 2 mem 1
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0
几个小时后,您可以看到/proc/net/sockstat 中的 TCP mem 值大幅攀升:
{
LISTEN: 37,
TIME_WAIT: 4151,
CLOSE_WAIT: 37,
ESTABLISHED: 337,
LAST_ACK: 1,
SYN_SENT: 1,
SYN_RECV: 4
}
sockets: used 897
TCP: inuse 171 orphan 0 tw 4151 alloc 413 mem 63487
UDP: inuse 2 mem 0
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0
几天后,mem 增长到大约 600k,接近我在/proc/sys/net/ipv4/tcp_mem 中看到的最大/临界阈值设置。 tcp inuse 值达到 5k 左右。服务器每秒处理大约十几个请求,因此微小/罕见的泄漏可能会在几天内变得很严重。
我尝试过每隔几分钟手动触发一次垃圾收集,以防这是由于奇怪/错误的 GC 调度(只是一个疯狂的猜测),但这没有帮助。
【问题讨论】:
-
您似乎将
await fetch()与fetch().catch()混合在一起。在异步函数中,您使用try {} catch (err){}来处理像fetch()这样的函数失败的情况。您可能有失败,但您没有记录它们。 -
@O.Jones 如果
.catch(e => ...)方法在功能上与这里的try{}catch(e){}不同,我会感到非常惊讶!我应该注意到在实际代码中我正在记录错误(事实上,我已经设置了一个监控服务,以便在出现错误时向我发送电子邮件——它们真的很少见)。我现在将编辑问题以指定这一点。 -
有了你的额外信息
Streams应该可以无缝地处理源和目标之间的同步;但是,如果您希望在连接仍在进行的情况下在一段时间后结束连接,那么您可以使用 node-fetch GH 页面上提到的 Request cancel with AbortSignal。结束响应也将是对response.end()的调用。 -
@JavadM.Amiri 我正在使用
node-fetch的超时选项(如我的代码示例所示),但实际上我在实际代码中也设置了一个中止信号(我最初添加它是因为我认为timeout选项可能存在错误)。我会将此信息添加到我的帖子中。
标签: javascript node.js stream node-fetch node-streams