如何解决CoroutineExceptionHandler对于OutOfMemoryError或其他致命错误应该怎么办?
我正在实现自定义的Kotlin CoroutineScope,用于处理通过WebSocket连接接收,处理和响应消息。范围的生命周期与WebSocket会话相关,因此只要WebSocket打开,它就处于活动状态。作为协程作用域上下文的一部分,我安装了custom exception handler,如果发生未处理的错误,它将关闭WebSocket会话。就像这样:
val handler = CoroutineExceptionHandler { _,exception ->
log.error("Closing WebSocket session due to an unhandled error",exception)
session.close(POLICY_VIOLATION)
}
令我惊讶的是,异常处理程序不仅接收异常,而且实际上为所有未处理的throwables调用,包括Error
的子类型。我不确定应该怎么做,因为我从Java API documentation for Error
知道“ Error
[...]表示严重的问题,合理的应用程序不应试图抓住“ 。
我最近遇到的一种特殊情况是OutOfMemoryError
,这是因为会话要处理的数据量大。我的OutOfMemoryError
收到了CoroutineExceptionHandler
,这意味着它已被记录并且WebSocket会话已关闭,但是应用程序继续运行。这让我感到不舒服,因为我知道在代码执行期间可以随时抛出OutOfMemoryError
,结果可能会使应用程序处于无法恢复的状态。
我的第一个问题是:为什么Kotlin API为什么选择将这些错误传递给CoroutineExceptionHandler
以便程序员(我)来处理?
直接从中得出的第二个问题是:处理该问题的合适方法是什么?我至少可以想到三个选择:
- 继续做我现在正在做的事情,这是要关闭发生错误的WebSocket会话,并希望应用程序的其余部分可以恢复。如我所说,这让我感到不舒服,尤其是当我阅读this one,in response to a question about catching
OutOfMemoryError
in Java之类的答案时,强烈建议不要尝试从此类错误中恢复。 - 重新抛出该错误,使其传播到线程。在正常(或框架)代码中遇到
Error
的任何其他情况下,我通常都会这样做,因为这最终会导致JVM崩溃。但是,在我的协程范围内(与一般的多线程一样),这不是一个选择。重新抛出异常只会最终将其发送到线程的UncaughtExceptionHandler
,而对此没有任何作用。 - 启动应用程序的完全关闭。停止应用程序似乎是最安全的操作,但是我想确保自己完全理解其中的含义。协程是否有任何机制可以将致命错误传播到应用程序的其余部分,还是我需要自己编写该功能的代码? Kotlin协程API设计人员是否考虑过“致命应用程序”错误的传播,或者在将来的发行版中可能会考虑这些错误?其他多线程模型通常如何处理此类错误?
解决方法
-
为什么Kotlin API选择将这些错误传递给
CoroutineExceptionHandler
,以便我(程序员)来处理?Kotlin中的所有异常类都是Throwable类的后代。
因此Kotlin文档似乎对所有
Throwable
,包括Error
使用术语 exception 。是否应传播协程中的异常实际上是选择协程生成器的结果(参见Exception propagation):
协程生成器有两种形式:自动传播异常(启动和演员)或向用户展示异常(异步和产生)。
如果在WebSocket范围内收到未处理的异常,则表明调用链中存在不可恢复的问题。可以在最可能的调用级别上处理可恢复的异常。因此,很自然地,您不知道如何在WebSocket范围内做出响应,并指出您正在调用的代码有问题。
协程函数然后选择安全路径并取消父作业(包括取消其子作业),如Cancellation and exceptions所述:
如果协程遇到CancellationException以外的其他异常,它将取消带有该异常的父对象。此行为不能被覆盖,并且用于为结构化并发提供稳定的协程层次结构。
-
对我来说合适的处理方式是什么?
在任何情况下:尝试首先将其记录下来(如您所愿)。考虑提供尽可能多的诊断数据(包括堆栈跟踪)。
请记住,协程库已经为您取消了作业。在许多情况下,这已经足够了。不要指望协程库能做更多的事情(不是现在,不是将来的版本)。它不具备做得更好的知识。应用服务器通常提供用于异常处理的配置,例如。像Ktor中一样。
除此之外,它还取决于并且可能涉及试探法和权衡取舍。不要盲目遵循“最佳做法”。您比其他人更了解您的应用程序的设计和要求。需要考虑的一些方面:
-
为实现高效运营,请自动且尽可能合理地无缝恢复受影响的服务。有时候,简单的方法(关闭并重新启动可能会受到影响的所有内容)就足够了。
-
评估从未知状态恢复的影响。仅仅是一个容易引起注意的小故障,还是人们的生活取决于结果?如果发生未捕获的异常:应用程序是否以释放资源和回滚事务的方式设计?依赖系统可以继续正常运行吗?
-
如果您可以控制调用的函数,则可以为可恢复的异常(仅具有暂时性和非破坏性的影响)引入单独的异常类(层次结构),并以不同的方式对待它们。
-
在尝试恢复部分工作的系统时,请考虑采用分阶段的方法并处理后续失败:
- 如果仅关闭协程就足够了,那就别管它了。您甚至可以保持WebSocket会话打开,并向客户端发送重启指示消息。考虑一下Kotlin协程文档中关于Supervision的章节。
- 如果这不安全(或者发生后续错误),请考虑关闭线程。这与分派到不同线程的协程无关,但是对于没有线程间耦合的系统来说,这是一个合适的解决方案。
- 如果这仍然是不安全的(或发生后续错误),请关闭整个JVM。这全都取决于异常的根本原因。
-
如果您的应用程序修改了持久性数据,请确保其设计防崩溃(例如,通过原子事务或其他自动恢复策略)。
-
如果整个应用程序的设计目标是防止崩溃,请考虑使用crash-only software design而不是(可能是复杂的)关闭过程。
-
在出现OutOfMemoryError的情况下,如果原因是奇异性(例如,一个巨大的分配),则恢复可以如上所述进行。另一方面,如果JVM甚至无法分配很小的位,则通过
Runtime.halt()
强行终止JVM可能会阻止级联的后续错误。
-
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。