对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的阿里云开发者社区。

目录
相关文章
|
7月前
|
Web App开发 前端开发
【前端异常】Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
【前端异常】Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
474 0
|
9月前
|
Java Maven Android开发
成功解决FATAL ERROR in native method: JDWP on getting class status, jvmtiError=JVMTI_ERROR_WRONG_PHASE
成功解决FATAL ERROR in native method: JDWP on getting class status, jvmtiError=JVMTI_ERROR_WRONG_PHASE
|
Java Android开发
Bad method handle type 7异常解决
在利用androidx版本写demo时,在添加了一些依赖后,遇到了`java.lang.ClassNotFoundException`bug,这就很奇怪了,我就添加rxjava3的依赖,就给我报这个错误。
|
Go iOS开发
The operation couldn’t be completed. Unable to log in with account 'myappleid'. An unexpected failure occurred while logging in (Underlying error code 1100).解决方法
The operation couldn’t be completed. Unable to log in with account 'myappleid'. An unexpected failure occurred while logging in (Underlying error code 1100).解决方法
362 0
|
自然语言处理
合同结构化文书解析失败,请联系管理员排查:{"code":3001,"message":"File transform error","success":false,"tracerId":"requestId"}报错处理
在使用自然语言处理自学习平台时,标注任务需要上传标注数据,但是使用doc格式上传文件后开始标注时出现了此提示,此篇文章简单介绍下此问题的处理方式。
622 0
合同结构化文书解析失败,请联系管理员排查:{"code":3001,"message":"File transform error","success":false,"tracerId":"requestId"}报错处理