node.js - JSONStream 期间的无限循环

Posted

技术标签:

【中文标题】node.js - JSONStream 期间的无限循环【英文标题】:node.js - infinite loop during JSONStream 【发布时间】:2015-12-28 23:15:03 【问题描述】:

我有一个 node.js 服务器在生产中冻结,它似乎是由 JSONStream 内部的无限循环引起的。这是从冻结服务器的核心转储中捕获的​​堆栈跟踪:

1: toString [buffer.js:~392] (this=0x1e28fb6d25c9 <a Buffer>#1#,encoding=0x266ee104121 <undefined>,start=0x266ee104121 <undefined>,end=0x266ee104121 <undefined>)
2: arguments adaptor frame: 0->3
3: write [/home/deploy/node_modules/JSONStream/node_modules/jsonparse/jsonparse.js:136] (this=0x32cc8dd5a999 <a Parser>#2#,buffer=0x32cc8dd5aa49 <a Buffer>#3#)
4: /* anonymous */ [/home/deploy/node_modules/JSONStream/index.js:~17] (this=0x32cc8dd5ab11 <a Stream>#4#,chunk=0x32cc8dd5aa49 <a Buffer>#3#)
5: write [/home/deploy/node_modules/JSONStream/node_modules/through/index.js:~24] (this=0x32cc8dd5ab11 <a Stream>#4#,data=0x32cc8dd5aa49 <a Buffer>#3#)
6: write [_stream_readable.js:~582] (this=0x266ee106c91 <JS Global Object>#5#,dest=0x32cc8dd5ab11 <a Stream>#4#,i=0,list=0x266ee104101 <null>)
7: flow [_stream_readable.js:592] (this=0x266ee106c91 <JS Global Object>#5#,src=0x32cc8dd5ac69 <an IncomingMessage>#6#)
8: /* anonymous */ [_stream_readable.js:560] (this=0x266ee106c91 <JS Global Object>#5#)
9: _tickCallback [node.js:415] (this=0x29e7331bb2a1 <a process>#7#)

如何找到这个无限循环的来源?

不幸的是,服务器正在生产中运行并处理数千个请求,因此很难提供任何额外的上下文。服务器的基本功能是为其他服务发出出站 HTTP 请求。

值得注意的是,我不认为这是由内存泄漏引起的。在这些冻结事件期间,服务器的内存使用率保持不变(并且很低),而 CPU 飙升至 99%

关于无限循环结束的另一个证据是事件循环本身似乎已经停止。当我将 console.log 放入 setInterval 时,服务器一冻结就会停止输出。

我们通过将最大连接数设置为 Infinity(这会禁用它们在 node.js 中的重用)来验证问题不是由过期/损坏的套接字连接引起的

我们使用的是 JSONStream 0.7.1(其中包括 0.0.5 的默认 jsonparse 版本)。我们在 JSONStream 存储库中找到了this issue,并尝试分叉 JSONParse 并仅更新到最新的 jsonparse 版本。它没有解决问题。

【问题讨论】:

第一条消息是toString [buffer.js:~392],问题出在buffer.js第392行 @JonathanS 可能导致无限循环的原因是什么?那个来源看起来很无害 您是否拥有要序列化到流中的 JSON 字符串本身? 没关系。您的问题似乎是由 here 引起的。 让我们continue this discussion in chat. 【参考方案1】:

您的问题似乎是由this line 或jsonstream@0.0.5 引起的。虽然我无法在不转储服务器的情况下为您最终解决问题,但这似乎表明您的缓冲区非常大。

这也可以解释为什么你的服务器被锁定(正如你在聊天中提到的那样),为什么事件循环没有继续,为什么你的内存没有上升到天空但你的 CPU 会;这里可能发生的情况是,您正在尝试 toString() 大量字节,而您的硬件根本无法做到这一点并且它会死掉。

请务必报告此线索的任何进一步调查。我觉得您将不可避免地在开发盒上重现该问题。它可以像向缓冲区添加一些健全性检查并确保它不超过特定大小一样简单。

上面的代码部分只有在n &lt;= 128 时才会被命中,所以如果你使用带符号的字符(例如 Unicode)或带符号的字节,那么你实际上可能会在正常功能中遇到这个问题......可能需要考虑!

【讨论】:

再次感谢!我已经设法验证我们确实在那条线上循环。无限循环的直接原因是 this.bytes_in_sequence 为 0,所以我们从不增加计数器 i,所以我们卡住了。我还没弄清楚那个 0 的根本原因,但是……进步了!! 这似乎是一个奇怪的错误。我链接的部分只有在bytes_in_sequence &lt;= 0 时才会到达,它实际上会减少i 1 - 这就是导致无限循环的原因。您可能想检查这是否在 jsonparse 的 github 上记录为问题。您肯定您尝试过更新版本的 jsonparse 吗?我的意思是,这个版本的 jsonparse 已经有 2 年历史了。 最后,有两个独立但相关的问题。首先,我们使用的 JSONStream 版本包括 jsonparse@0.0.5,它在 OP 中提到的修复之前。一旦我们应用了该修复程序,服务器仍然冻结,因为该修复程序处理了 128 到 194 之间的 ASCII 字符,但没有处理 244 以上的字符。tldr:一个特定的 ascii 字符导致 jsonparse 内部出现无限循环 多么有趣的错误:P @ZECTBynmo 似乎 we're not the first ones 有这个问题。只是没有人打开 PR。

以上是关于node.js - JSONStream 期间的无限循环的主要内容,如果未能解决你的问题,请参考以下文章

使用 JSONPath 和 JSONStream 解析 json 流

如何在 Node.js 中使用流对大型嵌套对象进行 JSON 字符串化?

如何在 node.js 断开连接期间缓冲 MongoDB 插入?

通过 es.map() 和 JSONStream.stringify() 将 JSONStream.parsed() 数据传输到文件流时,节点堆耗尽

使用 JSONStream 读取大型 JSON 文件

如何使用 JSONStream 对大对象进行字符串化