对Atmosphere中AtmosphereResourceImpl "Exception during suspend() operation"问题的追踪

简介: 在Atmosphere通信正常的情况下关闭浏览器,有时候会看到log出现一行Exception during suspend() operation的错误,虽然LOG是WARN的等级感觉应该不会有什么问题,但总觉得觉得应该有什么地方出了问题,为什么具体的错误行数被隐藏起来?

在Atmosphere通信正常的情况下关闭浏览器,有时候会看到log出现一行错误

[qtpxxx-xx] WARN org.atmosphere.cpr.AtmosphereResourceImpl - Exception during suspend() operation xxxException

看到WARN的等级感觉应该不会有什么问题,但总觉得觉得应该有什么地方出了问题,为什么具体的错误行数被隐藏起来?

我看的是2.4.13的源码,当然现在最新版的2.6.x我也看了,一样,这块没改,问我为什么不用2.5.x,根本用不了,不知道搞了什么鬼,2.5都跑不了直接开2.6,跟scala新版本的兼容也有很大的问题,只能用老版本的scala。

打开AtmosphereResourceImpl,直接搜索Exception during suspend就能找到报错的地方,它在665行,逐行调试 发现在onDisconnect调用时报错,跟踪进入onDisconnect的位置753行,看到下面这个方法。

void onDisconnect(AtmosphereResourceEvent e) {
    for (AtmosphereResourceEventListener r : listeners) {
        r.onDisconnect(e);
        if (transport.equals(TRANSPORT.WEBSOCKET) && WebSocketEventListener.class.isAssignableFrom(r.getClass())) {
            WebSocketEventListener.class.cast(r).onDisconnect(new WebSocketEventListener.WebSocketEvent(1005, CLOSE, webSocket));
        }
    }

    if (e.getResource() != null) {
        config.framework().notifyDestroyed(e.getResource().uuid());
    }
}

发现它主要是想关闭所有的Listener,正常情况下有两个Listener,如下:

  1. org.atmosphere.interceptor.HeartbeatInterceptor$2 (Heartbeat Interceptor Support)
  2. org.scalatra.atmosphere.ScalatraAtmosphereHandler$ScalatraResourceEventListener

第一个Listener正常关闭,第二个Listener触发空指针,进入第二个Listener的onDisconnect()看看发生了什么,这个Listener在scalatra-atmosphere下,我用的是_2.12:2.65的版本,也许你会好奇为什么有两个版本号,我用的是gradle,不同于sbt,在gradle这边的maven中央仓库中,scalatra的包自带版本号,所有的子包又有自己的版本号,我用过这么多库,也是头次见这种玩法,当然我检验过2.7.1新库,代码是一样的,而且Atmosphere本身都没法用,何况他只是一个桥接器,路径在org\scalatra\atmosphere\ScalatraAtmosphereHandler.scala ,更新了这么多版本,连注释的三行代码都一模一样

可以看到在36行发生报错,也就是调用client()时候

def onDisconnect(event: AtmosphereResourceEvent): Unit = {
  val disconnector = if (event.isCancelled) ClientDisconnected else ServerDisconnected
  client(event.getResource) foreach (_.receive.lift(Disconnected(disconnector, Option(event.throwable))))
  //      if (!event.getResource.isResumed) {
  //        event.getResource.session.invalidate()
  //      } else {
  event.getResource.session.removeAttribute(org.scalatra.atmosphere.AtmosphereClientKey)
  //      }
}

这个foreach可以改成直接get,不然不好调试,IDEA Evaluate对scala支持不佳

client(event.getResource).get.receive.lift(Disconnected(disconnector, Option(event.throwable)))

NPE就发生在这一行,那他到底在哪呢,client(event.getResource).get.receive得到了使用atmosphere时候定义的AtmosphereClient中的AtmoReceiveDisconnected(disconnector, Option(event.throwable))自然也是正常,那么问题出在哪呢,自然是调用AtmosphereClient时定义的AtmoReceive => case Disconnected出了错,这里出现任何非中止性的异常,都会被AtmosphereResourceImpl捕获,然后抛出Exception during suspend的WARN,但在DEBUG的日志等级下才会打印详细的堆栈信息。

在Vaadin上发现有人讨论这个问题,原来Vaadin也用了Atmosphere,https://vaadin.com/forum/thread/15195626/15321192 ,可以看到他们的一个产品经理Tatu Lund在二楼回复

If this exception happens due closing of the browser by the user it is harmless. The reason it has been logged is that the server cannot know now whether the application in browser has crashed or it has been just closed. Also it may be possible that the communication pipeline between server and browser has been disconnected for some reason.

Tatu Lund认为错误无关紧要,导致这行log的原因是不知道用户关闭浏览器时是因为崩溃还是正常关,亦或是通信管道意外断开。

但根据源码的逻辑来看,这并不是一个完全正确的解释,这个错误代表着Disconnected监听异常,也有可能意味着其他Listener出错,无论用户如何关浏览器,如何断开连接,在服务器端也不可能根据这些不同的意外情况,做出不同的处理,所以无论什么情况都不应该报出这个错误。这是调用Atmosphere时的Disconnected监听本身出了问题,Disconnected中可能发生什么操作呢?有可能是记录日志时出了问题,有可能是更新用户在线状态出了问题,有可能是分发给其他客户端出了问题,无论是什么问题,都会被AtmosphereResourceImpl拦截,统一抛出这个WARN。

楼主继续追问怎么关闭这个log,看了源码就知道这段WARN只要Disconnected监听异常就一定会打出来,没有其他控制,只能针对性改log level,调整到ERROR等级,或者排除这个类的log,因为这个类没有ERROR级别的错误。

这个AtmosphereResourceImpl类功能繁多,有一千多行,全类各方法合计抛出22个异常,值得一提的是,其中一个错误消息的链接指向已经失效了,在144行,最新版4.6在141行,是一个获取Session的问题,它指向 http://java.net/jira/browse/GLASSFISH-18856 ,然而java.net网站早已关闭,2016年Oracle就已经发布了关闭网站的公告,这个问题的讨论本来在Github上有,现在也被删掉了,在 https://github.com/eclipse-ee4j/glassfish/issues/18856 最下面有一个贴文指向https://github.com/javaee/glassfish/issues/18856 ,但现在已经是404了,JIRA GLASSFISH-18856到底是什么?解决了没有?不得而知。

最后我想说,Atmosphere不是一个值得选择的组件,它缺少维护,设计比较混乱,可惜被scalatra所支持,我也不想给官方提issue了,看到很多2014,2015年的issue到现在还没close,感觉这种小问题简直不值一提,log level在开发的时候也确实应该调成DEBUG,这样自然能轻松定位问题,但这个异常处理机制真不合理,谁知道线上会出什么问题呢,总不能也改成DEBUG,没有记录到准确的堆栈信息。也许这个问题该scalatra的桥接器来处理,毕竟是个很小的事,在onDisconnect包个try catch级别打印ERROR级日志即可,我看下面的liftAction()就有一个try catch,直接printStackTrace()出来了,说明这是可行的,但scalatra把所有子组件都放在自己的主项目中,拖家带口,解释起来比较麻烦,而且scalatra很大概率认为应该转给atmosphere来处理。

本文写作于2021年1月26日并发布于lyrieek的掘金,于2023年7月16日进行修订发布于lyrieek的阿里云开发者社区。

目录
相关文章
|
SQL Java Linux
聊聊 kerberos 的 kinit 命令和 ccache 机制
聊聊 kerberos 的 kinit 命令和 ccache 机制
|
传感器 机器学习/深度学习 人工智能
苏黎世理工最新!maplab2.0:模块化的多模态建图定位框架
将多传感器模态和深度学习集成到同时定位和mapping(SLAM)系统中是当前研究的重要领域。多模态是在具有挑战性的环境中实现鲁棒性和具有不同传感器设置的异构多机器人系统的互操作性的一块垫脚石。借助maplab 2.0,这个多功能的开源平台,可帮助开发、测试新模块和功能,并将其集成到一个成熟的SLAM系统中。
苏黎世理工最新!maplab2.0:模块化的多模态建图定位框架
|
3月前
|
人工智能 边缘计算 搜索推荐
从经验管理到智能分析:2025年健身房会员运营的数字化转型及工具选型
本简介介绍了健身房会员管理系统的四代技术演进,从纸质档案到AIoT智能系统的发展路径。分析了当前数字化管理的新需求,如多模态交互、智能合约与数字孪生等前沿技术应用。同时,系统讲解了智能会员管理系统的核心功能模块、关键技术实现与主流工具选型评估体系,并提出了系统实施策略与常见问题解决方案。展望未来,元宇宙、生成式AI和边缘计算将推动健身管理向更智能、个性化的方向发展,全面提升运营效率与会员体验。
269 0
|
1月前
|
并行计算 PyTorch 算法框架/工具
vLLM 架构学习指南
本指南深入解析vLLM高性能推理引擎架构,涵盖核心创新PagedAttention与连续批处理技术,结合代码结构、学习路径与实践建议,系统指导用户从入门到贡献源码的全过程。
509 2
vLLM 架构学习指南
|
8月前
|
人工智能 PyTorch API
Hunyuan3D 2.0:腾讯混元开源3D生成大模型!图生/文生秒建高精度模型,细节纹理自动合成
Hunyuan3D 2.0 是腾讯推出的大规模 3D 资产生成系统,专注于从文本和图像生成高分辨率的 3D 模型,支持几何生成和纹理合成。
960 5
Hunyuan3D 2.0:腾讯混元开源3D生成大模型!图生/文生秒建高精度模型,细节纹理自动合成
|
8月前
|
XML Java Maven
springboot-多环境配置文件
本文介绍了如何创建开发和生产环境的配置文件,并在IDEA和Maven中进行配置。开发环境中,通过设置profile为`dev`来指定配置文件;生产环境中,使用Maven命令参数`-Pprod`打包并指定配置文件。公共配置可放在`application.yml`中统一管理。日志配置需确保`logback-spring.xml`中的profile正确,以保证日志正常输出。
490 4
springboot-多环境配置文件
|
机器学习/深度学习 监控 数据挖掘
基于Django和百度飞桨模型的情感识别Web系统
基于Django和百度飞桨模型的情感识别Web系统
230 5
|
机器学习/深度学习 运维 监控
|
存储
深入解析AVL树:高效实现二叉平衡搜索树
深入解析AVL树:高效实现二叉平衡搜索树
229 1
|
UED C++ Python
GUI开发入门指南
GUI开发入门指南