undertow websocket 热加载报错断开链接问题

我使用最新版本的undertow-core-2.2.2.Final,在没有任何更新class的情况下,websocket能正常运行。当加载了class,即热加载后,日志中报错:

Loading changes ......

2021-02-19 15:06:19
[WARN]-[Thread: HotSwapWatcher]-[com.jfinal.server.undertow.session.HotSwapSessionPersistenceManager.persistSessions()]: UT015009: Failed to persist session attribute io.undertow.websocket.current-connections with value [WebSocket13Channel peer /0:0:0:0:0:0:0:1:53188 local /0:0:0:0:0:0:0:1:7025[ No Receiver [] -- [] -- []]] for session K4RSVT3_lsLkeaHCDJy8XhSX2Xcm0L5zrO2cZTBf
java.io.NotSerializableException: io.undertow.websockets.core.protocol.version13.WebSocket13Channel
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at java.util.ArrayList.writeObject(ArrayList.java:766)
	at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1140)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.jfinal.server.undertow.session.HotSwapSessionPersistenceManager.persistSessions(HotSwapSessionPersistenceManager.java:50)
	at io.undertow.servlet.handlers.SessionRestoringHandler.stop(SessionRestoringHandler.java:108)
	at io.undertow.servlet.core.DeploymentManagerImpl$3.call(DeploymentManagerImpl.java:617)
	at io.undertow.servlet.core.DeploymentManagerImpl$3.call(DeploymentManagerImpl.java:612)
	at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:42)
	at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
	at io.undertow.servlet.core.DeploymentManagerImpl.stop(DeploymentManagerImpl.java:626)
	at com.jfinal.server.undertow.UndertowServer.doStop(UndertowServer.java:415)
	at com.jfinal.server.undertow.UndertowServer.restart(UndertowServer.java:433)
	at com.jfinal.server.undertow.hotswap.HotSwapWatcher.doRun(HotSwapWatcher.java:133)
	at com.jfinal.server.undertow.hotswap.HotSwapWatcher.run(HotSwapWatcher.java:91)

2021-02-19 15:06:19
[ERROR]-[Thread: XNIO-3 I/O-2]-[org.xnio.ChannelListeners.invokeChannelListener()]: XNIO001007: A channel event listener threw an exception
java.util.concurrent.RejectedExecutionException: XNIO007007: Thread is terminating
	at org.xnio.nio.WorkerThread.execute(WorkerThread.java:590)
	at io.undertow.websockets.jsr.UndertowSession$3.handleEvent(UndertowSession.java:396)
	at io.undertow.websockets.jsr.UndertowSession$3.handleEvent(UndertowSession.java:388)
	at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92)
	at io.undertow.server.protocol.framed.AbstractFramedChannel$FrameCloseListener.handleEvent(AbstractFramedChannel.java:1075)
	at io.undertow.server.protocol.framed.AbstractFramedChannel$FrameCloseListener.handleEvent(AbstractFramedChannel.java:990)
	at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92)
	at org.xnio.conduits.WriteReadyHandler$ChannelListenerHandler.terminated(WriteReadyHandler.java:70)
	at org.xnio.nio.NioSocketConduit.writeTerminated(NioSocketConduit.java:234)
	at org.xnio.nio.NioSocketConduit.terminateWrites(NioSocketConduit.java:223)
	at org.xnio.nio.NioSocketConduit.truncateWrites(NioSocketConduit.java:228)
	at io.undertow.conduits.IdleTimeoutConduit.truncateWrites(IdleTimeoutConduit.java:387)
	at org.xnio.conduits.ConduitStreamSinkChannel.close(ConduitStreamSinkChannel.java:186)
	at org.xnio.IoUtils.safeClose(IoUtils.java:134)
	at org.xnio.conduits.WriteReadyHandler$ChannelListenerHandler.forceTermination(WriteReadyHandler.java:57)
	at org.xnio.nio.NioSocketConduit.forceTermination(NioSocketConduit.java:107)
	at org.xnio.nio.WorkerThread.run(WorkerThread.java:494)


然后页面上就收不到消息了,需要刷新页面才可以。热加载或者重新启动服务器后,websokcet都是closed状态,都需要刷新页面。

请问下这个报错如何处理?

websocket能不能一直处于连接状态?或者断开后自动连接上?

评论区

JFinal

2021-02-19 16:39

为了维持热加载以后 session 中的数据可以使用,在热加载之前 session 中的数据会被持久化,而持久化需要被持久化的对象实现 Serializable 这个接口

检查一下你存放在 session 中的哪些数据是没有实现这个接口的,注意,不仅是直接对象,还要检查间接对象

此外,你给的信息中其实只有一个 error,其中第一个为 WARN,第二个为 ERROR,第二个是 RejectedExecutionException ,注意查明原因

北流家园网

2021-02-19 20:07

@JFinal io.undertow.websockets.core.protocol.version13.WebSocket13Channel 提示中是说这个没有实现 Serializable 这个接口,检查过实体类,原来是@SuppressWarnings("serial")的,我改为实现Serializable ,也一样报这个错,而且自动生成的Model就是@SuppressWarnings("serial")的。我百度了很多也没有找到解决方法。

zzutligang

2021-02-27 01:14

@北流家园网,我也遇到这样的问题。后台只要修改代码触发热加载,就会抛这个异常。但因为我前端页面实现了断开自动重连,所以,从使用上来说,避免了出错。只是后台抛这个异常,看起来别扭!

北流家园网

2021-03-02 22:36

@zzutligang 有办法解决这个问题吗?

zzutligang

2021-03-03 11:29

@北流家园网 结合@JFinal的解释以及websocket的特性,websocket的session是不能序列化的。你这个问题,要通过两个步骤解决:第一:解决后台抛的异常问题,我观察在热加载的时候,其实是要回调onStop方法的,在这个方法里,关闭所有已经连上的websocket的session。这样就不会抛那个XNIO007007: Thread is terminating异常了。第二步,你的客户端一定要增加断线重连机制,我是通过发心跳实现的断线重连。这样,你后台一旦触发热部署,会先关闭所有已经连接的websocket,然后客户端会触发断线重连,就会重新连接上服务器。回复通信。

北流家园网

2021-04-02 22:04

@zzutligang 你好。关闭所有已经连上的websocket的session。这一点我不太理解,不懂如何关闭,请教一下,谢谢

zzutligang

2021-04-03 13:21

@北流家园网 首先,你后端要有一个保持所有连接对象的池子,我这里定义的是这样的:
private final Map connections = new ConcurrentHashMap<>(100);
其中WebSocket是在封装的对象,里面主要的成员就是session。
然后有连接,断开连接,你来维护这个connections变量。
最后在MainConfig的onStop里,遍历connections然后通过WebSocket里的session对象断开连接。
我的断开所有代码是这么写的:
public static void disconnectAll() {
Map webSockets = WebSocketManagerUtil.webSocketManager.localWebSocketMap();
CloseReason closeReason = new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE , "NORMAL_CLOSURE");
webSockets.forEach((identifier , webSocket) -> {
try {
webSocket.getSession().close(closeReason);
} catch (IOException e) {
e.printStackTrace();
}
});
webSockets.clear();
}