为啥异常会导致 Node.js 中的资源泄漏?
Posted
技术标签:
【中文标题】为啥异常会导致 Node.js 中的资源泄漏?【英文标题】:Why would an exception cause resource leaks in Node.js?为什么异常会导致 Node.js 中的资源泄漏? 【发布时间】:2013-03-27 09:15:43 【问题描述】:如果您查看 Node.js documentation for domains 的开头,它会声明:
由于 throw 在 javascript 中的工作原理,几乎没有任何方法可以安全地“从上次中断的地方继续”,而不会泄漏引用或创建其他类型的未定义脆弱状态。
再次在第一部分给出的代码示例中说:
虽然我们阻止了进程突然重启,但我们正在疯狂地泄漏资源
我想了解为什么会这样?哪些资源正在泄漏?他们建议您仅使用域来捕获错误并安全地关闭进程。这是所有例外的问题,而不仅仅是在使用域时?在 Javascript 中抛出和捕获异常是一种不好的做法吗?我知道这是 Python 中的常见模式。
编辑
我可以理解为什么如果您抛出异常,非垃圾收集语言中可能会出现资源泄漏,因为如果抛出异常,您可能运行来清理对象的任何代码都不会运行。
我能用 Javascript 想象的唯一原因是,如果抛出异常会将对变量的引用存储在引发异常的范围内(可能还有调用堆栈中的东西),从而保留引用,然后保留异常对象周围,永远不会被清理干净。除非提到的泄漏资源是引擎内部的资源。
更新
我写了一篇博客来解释这个问题的答案现在更好了。 Check it out
【问题讨论】:
你觉得这个问题很有用***.com/questions/14301839/… 不幸的是,这个问题只是谈论使用域来捕获异步异常。它根本没有提到内存泄漏。 这就是为什么它是评论而不是答案 :) 这是如何使域尝试/捕获更容易使用。至于问题,是关于封闭泄漏。你抛出一个异常,但是你有一个请求对象,它在一个事件中仍然有一个引用,并且该事件有对请求对象的引用,并且这两个不是垃圾收集的。例如,如果您有一个 MongoDB 连接,但由于抛出异常而没有关闭,它可能会隐式保持打开状态。 好点。因此,如果这是唯一的问题,那么听起来资源泄漏仅限于在有打开的流(套接字、文件等)的代码段中引发的异常。如果这是真的,我希望他们能更好地解释它,因为这样程序员就可以考虑到它。听起来 Javascript 需要 Python 的上下文管理器之类的东西。 虽然,垃圾收集器是为处理循环引用而构建的,所以一旦任何打开的流超出范围并且剩下的唯一引用是循环的,我认为它应该能够自动检测到这一点关闭信息流。 【参考方案1】:意外的异常是您需要担心的。如果您对应用程序的状态不够了解,无法添加对特定异常的处理并管理任何必要的状态清理,那么根据定义,您的应用程序的状态是未定义且不可知的,并且很可能存在一些事情不应该在附近闲逛。您需要担心的不仅仅是内存泄漏。未知的应用程序状态可能会导致不可预知的和不需要的应用程序行为(例如提供错误的输出——部分呈现的模板,或不完整的计算结果,或者更糟糕的是,每个后续输出都错误的情况)。这就是为什么在发生未处理的异常时退出进程很重要的原因。它让您的应用有机会自我修复。
发生异常,这很好。拥抱它。关闭进程并使用Forever 之类的东西来检测它并使事情回到正轨。集群和域也很棒。您正在阅读的文本不是对抛出异常的警告,也不是在您处理了预期的异常时继续进程的警告——它是在发生意外异常时防止进程继续运行的警告。
【讨论】:
感谢您的解释。实际上,我已经使用 forever 来管理我的流程了。我有一些使用 Socket.io 的服务器并维护一些活动的 websocket。如果出现异常,我担心必须关闭服务器,因为这意味着断开所有其他客户端。我尝试处理我能处理的每一个异常,并且我已经删除了我的全局异常处理程序。如果它因任何原因实际崩溃,我将永远使用它。再次感谢! 您不应该只删除全局异常处理程序。在退出之前记录异常仍然非常有用。 @chris 是的,但由于我永远使用它来管理我的日志,因此不处理它崩溃的异常,转储堆栈跟踪,然后永远只是重新启动它。所以我仍然将它记录下来,并且可以返回并查看堆栈跟踪。 +1 表示意外异常。是否有任何关于这种可能导致这些奇怪行为的意外异常的演示?听了很多,没见过。。 我现在没有时间创建一个,但是您可以简单地通过实例化任何东西来为自己创建一个(尝试读取一些大文件),然后在一个成功处理程序。然后创建一个引发错误的条件。例如,当您传递一个名为“err”的参数时抛出的中间件。 “忘记”处理错误情况并清理文件 refs。繁荣。内存泄漏。【参考方案2】:从 node.js 文档中获取示例:
var d = require('domain').create();
d.on('error', function(er)
// The error won't crash the process, but what it does is worse!
// Though we've prevented abrupt process restarting, we are leaking
// resources like crazy if this ever happens.
// This is no better than process.on('uncaughtException')!
console.log('error, but oh well', er.message);
);
d.run(function()
require('http').createServer(function(req, res)
handleRequest(req, res);
).listen(PORT);
);
在这种情况下,当您关闭套接字之前handleRequest
中发生异常时,您正在泄漏连接。
“泄漏”是指您完成了对请求的处理而没有事后清理。最终连接将超时并关闭套接字,但如果您的服务器处于高负载状态,它可能会在此之前耗尽套接字。
根据您在handleRequest
中所做的操作,您还可能会泄漏文件句柄、数据库连接、事件侦听器等。
理想情况下,您应该处理您的异常,以便在它们之后进行清理。
【讨论】:
感谢您的回答。它与其他人所说的一致,但这是一个很好的解释。【参考方案3】:我认为当他们说“我们正在泄漏资源”时,他们的真正意思是“我们可能正在泄漏资源”。如果 http.createServer 适当地处理异常,线程和套接字不应该被泄露。但是,如果它不能正确处理事情,它们当然可能是这样。在一般情况下,您永远真正知道某事是否始终正确处理错误。
我认为当他们说“由于 throw 在 JavaScript 中的工作原理......本质上,几乎没有任何方法可以安全......”时,他们是错误的/非常具有误导性。不应该有任何关于 throw 在 Javascript(相对于其他语言)中如何使其不安全的事情。也没有任何关于 throw/catch 的一般工作方式使其不安全 - 当然除非你错误地使用它们。
他们应该说的是,异常情况(无论是否使用异常)都需要适当处理。有几个不同的类别需要识别:
A.状态
-
外部状态(数据库写入、文件输出等)处于瞬态时发生的异常
共享内存处于瞬态时发生的异常
只有局部变量可能处于瞬态的例外情况
B.可逆性
-
可逆/可逆状态(例如数据库回滚)
不可逆状态(丢失数据、未知如何反转或禁止反转)
C.数据重要性
-
数据可以报废
必须使用数据(即使已损坏)
不管你正在搞乱哪种状态,如果你可以扭转它,你应该这样做并且你已经准备好了。问题是不可逆的状态。如果您可以销毁损坏的数据(或将其隔离以进行单独检查),那是实现不可逆状态的最佳举措。当抛出异常时,本地变量会自动完成此操作,这就是为什么异常擅长处理纯函数代码中的错误(即没有可能的副作用的函数)。同样,如果可以接受,则应删除任何共享状态或外部状态。在共享状态的情况下,要么抛出异常,直到该共享状态变为本地状态并通过展开堆栈(静态或通过 GC)来清理,要么重新启动程序(我读过有人建议使用某些东西永远喜欢nodejitsu)。对于外部状态,这可能更复杂。
最后一种情况是数据至关重要。好吧,那么你将不得不忍受你创造的错误。每个人都必须处理错误,但当您的错误涉及损坏的数据时,这是最糟糕的。这通常需要人工干预(重建丢失/损坏的数据,有选择地修剪等)——在最后一种情况下,异常处理不会让你完全走上正轨。
我写了一个类似的答案,涉及在对某些数据存储进行多次更新的情况下,如何在各种情况下处理中间操作失败:https://***.com/a/28355495/122422
【讨论】:
感谢您的回答。我非常同意。我认为文档充其量是误导性的,并且对此事非常难以描述。在第一次看到我在想的文档之后,如果它在语言中的工作方式存在固有问题,为什么它们会提供一种抛出和捕获异常的方法?这是一种常见的模式,其他语言似乎做得很好。我认为http.createServer()
没有办法正确处理异常并在域之前进行清理。我仍然坚信 Node.js 需要 Python 风格的上下文管理器。
顺便说一句,我昨晚写了一个blog,我刚刚更新了它并链接到你的答案。以上是关于为啥异常会导致 Node.js 中的资源泄漏?的主要内容,如果未能解决你的问题,请参考以下文章