我正在实现一个自定义 Kotlin CoroutineScope,它处理通过 WebSocket 连接接收、处理和响应消息。范围的生命周期与 WebSocket session 相关联,因此只要 WebSocket 处于打开状态,它就会处于事件状态。作为协程范围上下文的一部分,我安装了一个 custom exception handler,如果出现未处理的错误,它将关闭 WebSocket session 。它是这样的:
val handler = CoroutineExceptionHandler { _, exception ->
log.error("Closing WebSocket session due to an unhandled error", exception)
session.close(POLICY_VIOLATION)
}
我惊讶地发现异常处理程序不仅接收异常,而且实际上为所有未处理的 throwable 调用,包括
Error 的子类型。我不确定我应该如何处理这些,因为我从
Java API documentation for Error 中知道“
Error [...] 表示合理的应用程序不应该 try catch 的严重问题”。
我最近遇到的一个特殊情况是
OutOfMemoryError ,因为 session 处理的数据量很大。我的
OutOfMemoryError 收到了
CoroutineExceptionHandler ,这意味着它被记录并关闭了 WebSocket session ,但应用程序继续运行。这让我很不舒服,因为我知道
OutOfMemoryError 可以在代码执行期间的任何时候抛出,因此会使应用程序处于不可恢复的状态。
我的第一个问题是:为什么 Kotlin API 选择将这些错误传递给
CoroutineExceptionHandler 供我这个程序员处理?
我的第二个问题,直接从那之后,是:我处理它的合适方法是什么?我至少能想到三个选择:
OutOfMemoryError in Java 之类的答案时,强烈建议不要尝试从此类错误中恢复。 Error 的情况,因为它最终会导致 JVM 崩溃。但是,在我的协程范围内(与一般的多线程一样),这不是一个选项。重新抛出异常只会将它发送到线程的 UncaughtExceptionHandler ,它不会对它做任何事情。 请您参考如下方法:
CoroutineExceptionHandler 供我这个程序员处理?Kotlin docs on exceptions 状态:
All exception classes in Kotlin are descendants of the class Throwable.
因此,Kotlin 文档似乎对各种
Throwable 使用术语异常,包括 Error 。是否应该传播协程中的异常实际上是选择协程构建器的结果(参见 Exception propagation ):
Coroutine builders come in two flavors: propagating exceptions automatically (launch and actor) or exposing them to users (async and produce).
如果您在 WebSocket 范围内收到未处理的异常,则表明调用链中存在不可恢复的问题。期望在最接近的调用级别处理可恢复的异常。因此,很自然地,您不知道如何在 WebSocket 范围内做出响应并指出您正在调用的代码存在问题。
然后协程函数选择安全路径并取消父作业(包括取消其子作业),如 Cancellation and exceptions 中所述:
If a coroutine encounters an exception other than CancellationException, it cancels its parent with that exception. This behaviour cannot be overridden and is used to provide stable coroutines hierarchies for structured concurrency.
在任何情况下:尝试先记录它(就像你已经做的那样)。考虑提供尽可能多的诊断数据(包括堆栈跟踪)。
请记住,协程库已经为您取消了作业。在许多情况下,这已经足够了。不要指望协程库做更多的事情(不是现在,也不是在 future 的版本中)。它没有知识可以做得更好。应用服务器通常提供异常处理的配置,例如就像在 Ktor 中一样。
除此之外,它取决于,并且可能涉及启发式和权衡。不要盲目遵循“最佳实践”。您比其他人更了解您的应用程序的设计和要求。需要考虑的一些方面:
Runtime.halt() 强制终止 JVM 可能会防止级联后续错误。



