当前位置: 首页 > 知识库问答 >
问题:

CoroutineExceptionHandler应该如何处理OutOfMemoryError或其他致命错误?

景俊拔
2023-03-14

我正在实现一个定制的Kotlin CoroutineScope,它处理通过WebSocket连接接收、处理和响应消息。作用域的生命周期与WebSocket会话相关联,因此只要WebSocket处于打开状态,它就处于活动状态。作为协同程序作用域上下文的一部分,我安装了一个自定义异常处理程序,如果出现未处理的错误,它将关闭WebSocket会话。是这样的:

kotlin prettyprint-override">val handler = CoroutineExceptionHandler { _, exception -> 
    log.error("Closing WebSocket session due to an unhandled error", exception)
    session.close(POLICY_VIOLATION)
}

我惊讶地发现异常处理程序不仅接收异常,而且实际上为所有未处理的抛出物调用,包括Error的子类型。我不确定我应该如何处理这些,因为我从JavaAPI留档为错误,一个错误[...]表示严重的问题,一个合理的应用程序不应该试图捕捉。

我最近遇到的一个特殊情况是由于会话处理的数据量而导致的OutOfMemoryError。我的CoroutineExceptionHandler收到了OutOfMemoryError,这意味着它被记录并且WebSocket会话被关闭,但是应用程序继续运行。这让我很不舒服,因为我知道一个OutOfMemoryError可以在代码执行期间的任何时间点被抛出,结果可能会使应用程序处于不可恢复的状态。

我的第一个问题是:为什么Kotlin API选择将这些错误传递给CoroutineExceptionHandler,让程序员自己来处理?

我的第二个问题,直接从这一点出发,是:我处理它的合适方式是什么?我至少可以想到三种选择:

  1. 继续做我现在正在做的事情,即关闭引发错误的WebSocket会话,并希望应用程序的其余部分能够恢复。正如我所说,这让我感到不舒服,尤其是当我读到像这样的答案时,我回答了一个关于在Java中捕获OutOfMemoryError的问题,该问题强烈建议不要尝试从此类错误中恢复
  2. 重新抛出错误,让它传播到线程。这是我在正常(或框架)代码中遇到错误的任何其他情况下通常会做的,因为它最终会导致JVM崩溃。不过,在我的协同程序范围内(与通常的多线程一样),这不是一个选项。重新抛出异常只会将其发送到线程的UncaughtExceptionHandler,而该线程对异常没有任何作用
  3. 启动应用程序的完全关闭。停止应用程序感觉是最安全的做法,但我想确保我完全理解其中的含义。是否有任何机制可以让协同程序将致命错误传播到应用程序的其余部分,或者我需要自己编写该功能的代码?是“应用致命”错误的传播吗?其他多线程模型通常如何处理此类错误

共有1个答案

寿飞飙
2023-03-14

>

  • 为什么静态编程语言API选择将这些错误传递给CoroutineExceptionHandler让我程序员来处理?

    静态编程语言文档的异常状态:

    Kotlin中的所有异常类都是Throwable类的后代。

    因此,静态编程语言留档似乎对所有类型的Throwable使用了术语异常,包括Error

    是否应该传播协同程序中的异常实际上是选择协同程序生成器的结果(参见异常传播):

    协程生成器有两种类型:自动传播异常(启动和执行)或向用户公开异常(异步和生成)。

    如果在WebSocket作用域中收到未经处理的异常,则表明调用链中存在不可恢复的问题。可恢复异常预计将在最接近的调用级别上处理。因此,很自然,您不知道如何在WebSocket范围内做出响应,并指出您正在调用的代码存在问题。

    然后,协同程序功能选择安全路径并取消父作业(包括取消其子作业),如取消和例外中所述:

    如果一个协程遇到了一个异常,而不是取消异常,它会取消它的父异常。此行为不能被重写,用于为结构化并发提供稳定的协程层次结构。

    我怎样处理才合适呢?

    无论如何:试着先记录它(就像你已经做的那样)。考虑提供尽可能多的诊断数据(包括堆栈跟踪)。

    记住,协同程序库已经为您取消了作业。在很多情况下,这就足够了。不要指望协同程序库能做得比这更多(不是现在,不是在未来的版本中)。它没有做得更好的知识。应用服务器通常为异常处理提供配置,例如在Ktor中。

    除此之外,这还要视情况而定,并可能涉及启发式和权衡。不要盲目遵循“最佳实践”。您比其他人更了解应用程序的设计和需求。需要考虑的一些方面:

    >

    评估从未知状态恢复的影响。这只是一个容易被注意到的小故障,还是人们的生活取决于结果?对于未捕获的异常:应用程序的设计方式是释放资源并回滚事务吗?依赖系统能不受影响地继续吗?

    如果对调用的函数有控制权,则可以为可恢复异常(仅具有暂时和非破坏性影响)引入单独的异常类(层次结构),并以不同的方式处理它们。

    当试图恢复部分工作的系统时,考虑分阶段的方法并处理后续故障:

    • 如果只关闭您的协同程序就足够了,那么就到此为止。您甚至可以保持WebSocket会话打开,并向客户端发送重启指示消息。请考虑KOTLIN协同程序文档中有关监督的章节。<李>

    如果您的应用程序修改了持久数据,请确保它在设计上是防崩溃的(例如,通过原子事务或其他自动恢复策略)。

    如果整个应用程序的设计目标是防撞的,考虑只崩溃的软件设计,而不是(可能复杂的)关闭程序。

    在OutOfMemoryError的情况下,如果原因是奇点(例如一个巨大的分配),恢复可以按上述步骤进行。另一方面,如果JVM甚至不能分配微小的位,通过Runtime.halt()强制终止JVM可能会防止级联后续错误。

  •  类似资料:
    • 用 assert 测试编码和设计错误。如果其返回false,则程序终止,应纠正代码。这种方法在调试时很有用处。 忽略异常,这不适合公开发布的软件产品和任务关键的专用软件。但自用软件通常可以忽略许多错误。 退出程序,使程序无法运行完毕或产生错误结果。实际上,对于许多错误类型,这是个好办法,特别是对于能让程序运行完毕的非致命错误,因为让程序运行完毕很可能使程序员误以为程序工作很顺利。这种方法也不适合任

    • 注意在前面的例子中,我们对调用 parse 的最直接反应就是将错误从库错误映射到我们的新的自定义错误类型(原文:Notice in the previous example that our immediate reaction to calling parse is to map the error from a library error into our new custom error t

    • 问题内容: 我正在尝试将对象保存到数据库中,但是抛出错误。 问题出在表格内,用一个复选框表示。如果未选中该复选框,则显然不传递任何内容。这就是错误被排除的地方。 我如何正确处理并捕获此异常? 该行是 问题答案: 使用MultiValueDict的方法。这在标准字典中也存在,并且是一种在不存在默认值的情况下获取值的方法。 通常,

    • 问题内容: 我正在尝试将对象保存到数据库中,但是抛出错误。 问题出在表格内,用一个复选框表示。如果未选中该复选框,则显然不传递任何内容。这就是错误被排除的地方。 我如何正确处理并捕获此异常? 该行是 问题答案: 使用t的get方法。这在标准字典中也存在,并且是一种在不存在默认值的情况下获取值的方法。 通常,

    • 问题内容: 如何使用 自定义* 错误处理程序处理 解析 和 致命 错误? * 问题答案: 简单答案:不能。参见手册: 用户定义的函数无法处理以下错误类型:E_ERROR,E_PARSE,E_CORE_ERROR,E_CORE_WARNING,E_COMPILE_ERROR,E_COMPILE_WARNING,以及在调用set_error_handler()的文件中引发的大多数E_STRICT。 对

    • 问题内容: 根据对此答案的评论,有可能通过关机功能来捕获致命错误,而使用不能捕获该错误。 但是,我无法确定如何确定是由于致命错误还是由于脚本到达末尾而导致关机。 另外,调试回溯函数似乎在关闭函数中已失效,因此对于记录发生致命错误的堆栈跟踪而言,它毫无价值。 所以我的问题是:对致命错误(尤其是未定义的函数调用)做出反应的同时保持创建适当回溯能力的最佳方法是什么? 问题答案: 这对我有用: 但是,您可