Sam*_*Sam 5 error-handling jvm out-of-memory kotlin kotlin-coroutines
我正在实现一个自定义 Kotlin CoroutineScope,用于处理通过 WebSocket 连接接收、处理和响应消息。该作用域的生命周期与 WebSocket 会话相关,因此只要 WebSocket 打开,它就处于活动状态。作为协程作用域上下文的一部分,我安装了一个自定义异常处理程序,如果存在未处理的错误,该处理程序将关闭 WebSocket 会话。是这样的:
val handler = CoroutineExceptionHandler { _, exception ->
log.error("Closing WebSocket session due to an unhandled error", exception)
session.close(POLICY_VIOLATION)
}
Run Code Online (Sandbox Code Playgroud)
我惊讶地发现异常处理程序不仅接收异常,而且实际上为所有未处理的可抛出对象(包括Error. 我不确定应该如何处理这些,因为我从Java API 文档Error中知道“一个Error[...] 表明合理的应用程序不应尝试捕获的严重问题”。
我最近遇到的一个特殊情况是OutOfMemoryError由于会话处理的数据量所致。OutOfMemoryErrormy 收到了,这CoroutineExceptionHandler意味着它已被记录并且 WebSocket 会话已关闭,但应用程序继续运行。这让我感到不舒服,因为我知道OutOfMemoryError在代码执行期间的任何时候都可以抛出 an ,从而导致应用程序处于不可恢复的状态。
我的第一个问题是:为什么 Kotlin API 选择将这些错误传递给CoroutineExceptionHandler我(程序员)来处理?
我的第二个问题是:我处理它的适当方法是什么?我至少可以想到三个选择:
OutOfMemoryError在 Java 中捕获的问题的回答,该问题强烈建议不要尝试从此类错误中恢复。Error正常(或框架)代码的任何其他情况下通常会做的事情,因为它最终会导致 JVM 崩溃。不过,在我的协程范围内(与一般的多线程一样),这不是一个选择。重新抛出异常最终只会将其发送到线程 UncaughtExceptionHandler的,而线程不会对其执行任何操作。为什么 Kotlin API 选择将这些错误传递给CoroutineExceptionHandler我(程序员)来处理?
Kotlin 中的所有异常类都是 Throwable 类的后代。
因此,Kotlin 文档似乎对所有类型的异常都使用了术语“异常”Throwable,包括Error.
协程中的异常是否应该传播实际上是选择协程构建器的结果(参见异常传播):
协程构建器有两种类型:自动传播异常(启动和执行者)或将异常暴露给用户(异步和生成)。
如果您在 WebSocket 范围内收到未处理的异常,则表明调用链中存在不可恢复的问题。可恢复的异常预计在最接近的可能调用级别进行处理。因此,您很自然地不知道如何在 WebSocket 范围内做出响应,并且表明您正在调用的代码存在问题。
然后,协程函数选择安全路径并取消父作业(包括取消其子作业),如取消和例外中所述:
如果协程遇到 CancellationException 以外的异常,它会取消带有该异常的父级。此行为无法重写,用于为结构化并发提供稳定的协程层次结构。
我的处理方式合适吗?
无论如何:尝试先记录它(就像您已经做的那样)。考虑提供尽可能多的诊断数据(包括堆栈跟踪)。
请记住,协程库已经为您取消了作业。在许多情况下,这已经足够了。不要指望协程库能做更多的事情(不是现在,也不是将来的版本)。它不具备做得更好的知识。应用程序服务器通常提供异常处理的配置,例如在Ktor中。
除此之外,这取决于,并且可能涉及启发法和权衡。不要盲目遵循“最佳实践”。您比其他人更了解您的应用程序的设计和要求。需要考虑的一些方面:
为了高效运营,尽可能快速、无缝地自动恢复受影响的服务。有时,简单的方法(关闭并重新启动可能受影响的所有内容)就足够了。
评估从未知状态恢复的影响。这只是一个很容易注意到的小故障,还是人们的生命取决于结果?如果出现未捕获的异常:应用程序的设计方式是否可以释放资源并回滚事务?依赖系统可以不受影响地继续运行吗?
如果您可以控制调用的函数,则可以为可恢复的异常(仅具有暂时性和非破坏性的效果)引入单独的异常类(层次结构),并以不同的方式对待它们。
当尝试恢复部分工作的系统时,请考虑分阶段方法并处理后续故障:
如果您的应用程序修改持久性数据,请确保它在设计上是防崩溃的(例如通过原子事务或其他自动恢复策略)。
如果整个应用程序的设计目标是防崩溃,请考虑仅崩溃的软件设计,而不是(可能很复杂)的关闭程序。
在发生 OutOfMemoryError 的情况下,如果原因是奇点(例如一个巨大的分配),恢复可以如上所述分阶段进行。另一方面,如果 JVM 甚至无法分配微小的位,则通过强制终止 JVMRuntime.halt()可能会防止级联后续错误。