CoroutineExceptionHandler对于OutOfMemoryError或其他致命错误应该怎么办?

如何解决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以便程序员(我)来处理?

直接从中得出的第二个问题是:处理该问题的合适方法是什么?我至少可以想到三个选择:

  1. 继续做我现在正在做的事情,这是要关闭发生错误的WebSocket会话,并希望应用程序的其余部分可以恢复。如我所说,这让我感到不舒服,尤其是当我阅读this one,in response to a question about catching OutOfMemoryError in Java之类的答案时,强烈建议不要尝试从此类错误中恢复。
  2. 重新抛出该错误,使其传播到线程。在正常(或框架)代码中遇到Error的任何其他情况下,我通常都会这样做,因为这最终会导致JVM崩溃。但是,在我的协程范围内(与一般的多线程一样),这不是一个选择。重新抛出异常只会最终将其发送到线程的 UncaughtExceptionHandler,而对此没有任何作用。
  3. 启动应用程序的完全关闭。停止应用程序似乎是最安全的操作,但是我想确保自己完全理解其中的含义。协程是否有任何机制可以将致命错误传播到应用程序的其余部分,还是我需要自己编写该功能的代码? Kotlin协程API设计人员是否考虑过“致命应用程序”错误的传播,或者在将来的发行版中可能会考虑这些错误?其他多线程模型通常如何处理此类错误?

解决方法

  1. 为什么Kotlin API选择将这些错误传递给CoroutineExceptionHandler,以便我(程序员)来处理?

    Kotlin docs on exceptions状态:

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

    因此Kotlin文档似乎对所有Throwable,包括Error使用术语 exception

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

    协程生成器有两种形式:自动传播异常(启动和演员)或向用户展示异常(异步和产生)。

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

    协程函数然后选择安全路径并取消父作业(包括取消其子作业),如Cancellation and exceptions所述:

    如果协程遇到CancellationException以外的其他异常,它将取消带有该异常的父对象。此行为不能被覆盖,并且用于为结构化并发提供稳定的协程层次结构。

  2. 对我来说合适的处理方式是什么?

    在任何情况下:尝试首先将其记录下来(如您所愿)。考虑提供尽可能多的诊断数据(包括堆栈跟踪)。

    请记住,协程库已经为您取消了作业。在许多情况下,这已经足够了。不要指望协程库能做更多的事情(不是现在,不是将来的版本)。它不具备做得更好的知识。应用服务器通常提供用于异常处理的配置,例如。像Ktor中一样。

    除此之外,它还取决于并且可能涉及试探法和权衡取舍。不要盲目遵循“最佳做法”。您比其他人更了解您的应用程序的设计和要求。需要考虑的一些方面:

    • 为实现高效运营,请自动且尽可能合理地无缝恢复受影响的服务。有时候,简单的方法(关闭并重新启动可能会受到影响的所有内容)就足够了。

    • 评估从未知状态恢复的影响。仅仅是一个容易引起注意的小故障,还是人们的生活取决于结果?如果发生未捕获的异常:应用程序是否以释放资源和回滚事务的方式设计?依赖系统可以继续正常运行吗?

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

    • 在尝试恢复部分工作的系统时,请考虑采用分阶段的方法并处理后续失败:

      • 如果仅关闭协程就足够了,那就别管它了。您甚至可以保持WebSocket会话打开,并向客户端发送重启指示消息。考虑一下Kotlin协程文档中关于Supervision的章节。
      • 如果这不安全(或者发生后续错误),请考虑关闭线程。这与分派到不同线程的协程无关,但是对于没有线程间耦合的系统来说,这是一个合适的解决方案。
      • 如果这仍然是不安全的(或发生后续错误),请关闭整个JVM。这全都取决于异常的根本原因。
    • 如果您的应用程序修改了持久性数据,请确保其设计防崩溃(例如,通过原子事务或其他自动恢复策略)。

    • 如果整个应用程序的设计目标是防止崩溃,请考虑使用crash-only software design而不是(可能是复杂的)关闭过程。

    • 在出现OutOfMemoryError的情况下,如果原因是奇异性(例如,一个巨大的分配),则恢复可以如上所述进行。另一方面,如果JVM甚至无法分配很小的位,则通过Runtime.halt()强行终止JVM可能会阻止级联的后续错误。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-