对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中的AtmoReceive,Disconnected(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:模块化的多模态建图定位框架
|
应用服务中间件 Linux nginx
Nginx系列教程(06) - 配置反向代理
Nginx系列教程(06) - 配置反向代理
638 0
|
消息中间件 存储 运维
Zabbix与ELK整合实现对安全日志数据的实时监控告警
Zabbix与ELK整合实现对安全日志数据的实时监控告警
Zabbix与ELK整合实现对安全日志数据的实时监控告警
|
人工智能 边缘计算 搜索推荐
从经验管理到智能分析:2025年健身房会员运营的数字化转型及工具选型
本简介介绍了健身房会员管理系统的四代技术演进,从纸质档案到AIoT智能系统的发展路径。分析了当前数字化管理的新需求,如多模态交互、智能合约与数字孪生等前沿技术应用。同时,系统讲解了智能会员管理系统的核心功能模块、关键技术实现与主流工具选型评估体系,并提出了系统实施策略与常见问题解决方案。展望未来,元宇宙、生成式AI和边缘计算将推动健身管理向更智能、个性化的方向发展,全面提升运营效率与会员体验。
808 0
|
11月前
|
Prometheus 监控 Ubuntu
Python Requests库实战:API请求的重试机制与超时配置全解析
本文以顺丰API调用为例,深入剖析电商物流、金融监控等场景下API请求的稳定性问题。针对网络抖动、超时、封禁等常见故障,系统讲解Requests库的高级用法:精准设置连接与读取超时、实现指数退避重试与熔断机制、构建动态代理池防封禁,并结合签名认证与日志监控,打造高可用的API请求方案,全面提升系统健壮性。
862 1
|
负载均衡 网络协议 Linux
LVS,软负载均衡
LVS(Linux Virtual Server)是一项广泛应用的负载均衡技术,由章文嵩博士于1998年发起,自Linux 2.4.24版本起成为官方内核的一部分。LVS通过四层负载均衡技术实现高性能、高可用的服务器集群,支持多种调度算法和工作模式(如D-NAT、full-NAT、IP隧道、DR),适用于HTTP、数据库等应用。相比7层负载均衡器(如Nginx、HAProxy),LVS具有更高的并发处理能力和更低的资源消耗,适合大规模流量分发。本期文章详细介绍了LVS的工作原理、优势与不足,并对比了常见的负载均衡产品,帮助读者根据具体需求选择合适的解决方案。
2962 6
LVS,软负载均衡
|
机器学习/深度学习 运维 监控
|
算法 数据可视化 Python
使用 Python 模拟蒙特卡洛实验
使用 Python 模拟蒙特卡洛实验
856 1
|
存储 JavaScript 前端开发
听说在 Vue 3 中实现动态表单很难?作为一个前端开发者,表示一切都是小意思!!
听说在 Vue 3 中实现动态表单很难?作为一个前端开发者,表示一切都是小意思!!