Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Websocket not closed correctly on java.net.SocketException: Connection reset #7703

Closed
Neoministein opened this issue Oct 2, 2023 · 6 comments
Assignees
Labels
4.x Version 4.x bug Something isn't working P2 websocket WebSocket in Helidon

Comments

@Neoministein
Copy link

Environment Details

  • Helidon Version: 4.0.0-RC1
  • Helidon MP
  • JDK version: 21
  • OS: Win 10
  • Docker version (if applicable): -

Problem Description

I am not quite sure if this belongs here or on in the tyrus GitHub.

Setup:

I've got a websocket endpoint that I've registered with the @ServerEndpoint annoation. I've also got a method annotated with @OnClose.

When the websocket client crashes very hard it produces:

java.io.UncheckedIOException: java.net.SocketException: Connection reset
	at io.helidon.common.socket.PlainSocket.get(PlainSocket.java:150)
	at io.helidon.common.socket.PlainSocket.get(PlainSocket.java:32)
	at io.helidon.common.buffers.DataReader.pullData(DataReader.java:81)
	at io.helidon.common.buffers.DataReader.ensureAvailable(DataReader.java:111)
	at io.helidon.common.buffers.DataReader.readBuffer(DataReader.java:187)
	at io.helidon.microprofile.tyrus.TyrusConnection.handle(TyrusConnection.java:78)
	at io.helidon.webserver.http1.Http1Connection.handle(Http1Connection.java:160)
	at io.helidon.webserver.ConnectionHandler.run(ConnectionHandler.java:113)
	at io.helidon.common.task.InterruptableTask.call(InterruptableTask.java:47)
	at io.helidon.webserver.ThreadPerTaskExecutor$ThreadBoundFuture.run(ThreadPerTaskExecutor.java:239)
	at java.base/java.lang.VirtualThread.run(VirtualThread.java:311)
Caused by: java.net.SocketException: Connection reset
	at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:318)
	at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:346)
	at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:796)
	at java.base/java.net.Socket$SocketInputStream.read(Socket.java:1099)
	at io.helidon.common.socket.IdleInputStream.read(IdleInputStream.java:74)
	at java.base/java.io.InputStream.read(InputStream.java:220)
	at io.helidon.common.socket.PlainSocket.get(PlainSocket.java:142)
	... 10 more

The exception is caught and handled by io.helidon.microprofile.tyrus.TyrusConnection.TyrusListener.onError(). The onError method then calls this.connection.close(...) which then further down calls org.glassfish.tyrus.core.ProtocolHandler.close:

    public synchronized Future<Frame> close(final int code, final String reason) {

        ...

        final Future<Frame> send = send(outgoingCloseFrame, null, CLOSE, false); //<-- Throws exception

        webSocket.onClose(new CloseFrame(closeReason));

        return send;
    }

The close method then tries so send a closing frame back to the client but since a connection does no longer exists this exception is thrown and the @OnClose annotated method is never called:

java.io.UncheckedIOException: java.net.SocketException: Connection reset by peer
	at io.helidon.common.buffers.FixedBufferData.writeTo(FixedBufferData.java:74) ~[helidon-common-buffers-4.0.0-RC1.jar:4.0.0-RC1]
	at io.helidon.common.socket.PlainSocket.write(PlainSocket.java:136) ~[helidon-common-socket-4.0.0-RC1.jar:4.0.0-RC1]
	at io.helidon.common.socket.SocketWriter.writeNow(SocketWriter.java:77) ~[helidon-common-socket-4.0.0-RC1.jar:4.0.0-RC1]
	at io.helidon.microprofile.tyrus.TyrusConnection$TyrusListener$1.write(TyrusConnection.java:193) ~[helidon-microprofile-websocket-4.0.0-RC1.jar:4.0.0-RC1]
	at org.glassfish.tyrus.spi.Writer.write(Writer.java:62) ~[tyrus-spi-2.0.4.jar:?]
	at org.glassfish.tyrus.core.ProtocolHandler.write(ProtocolHandler.java:496) ~[tyrus-core-2.0.4.jar:?]
	at org.glassfish.tyrus.core.ProtocolHandler.send(ProtocolHandler.java:251) ~[tyrus-core-2.0.4.jar:?]
	at org.glassfish.tyrus.core.ProtocolHandler.close(ProtocolHandler.java:479) ~[tyrus-core-2.0.4.jar:?]
	at org.glassfish.tyrus.core.TyrusWebSocket.close(TyrusWebSocket.java:244) ~[tyrus-core-2.0.4.jar:?]
	at org.glassfish.tyrus.core.TyrusWebSocketEngine$TyrusConnection.close(TyrusWebSocketEngine.java:825) ~[tyrus-core-2.0.4.jar:?]
	at io.helidon.microprofile.tyrus.TyrusConnection$TyrusListener.onError(TyrusConnection.java:179) ~[helidon-microprofile-websocket-4.0.0-RC1.jar:4.0.0-RC1]
	at io.helidon.microprofile.tyrus.TyrusConnection.handle(TyrusConnection.java:84) ~[helidon-microprofile-websocket-4.0.0-RC1.jar:4.0.0-RC1]
	at io.helidon.webserver.http1.Http1Connection.handle(Http1Connection.java:160) ~[helidon-webserver-4.0.0-RC1.jar:4.0.0-RC1]
	at io.helidon.webserver.ConnectionHandler.run(ConnectionHandler.java:113) ~[helidon-webserver-4.0.0-RC1.jar:4.0.0-RC1]
	at io.helidon.common.task.InterruptableTask.call(InterruptableTask.java:47) ~[helidon-common-task-4.0.0-RC1.jar:4.0.0-RC1]
	at io.helidon.webserver.ThreadPerTaskExecutor$ThreadBoundFuture.run(ThreadPerTaskExecutor.java:239) ~[helidon-webserver-4.0.0-RC1.jar:4.0.0-RC1]
	at java.lang.VirtualThread.run(VirtualThread.java:311) ~[?:?]
Caused by: java.net.SocketException: Connection reset by peer
	at sun.nio.ch.SocketDispatcher.write0(Native Method) ~[?:?]
	at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:54) ~[?:?]
	at sun.nio.ch.NioSocketImpl.tryWrite(NioSocketImpl.java:394) ~[?:?]
	at sun.nio.ch.NioSocketImpl.implWrite(NioSocketImpl.java:410) ~[?:?]
	at sun.nio.ch.NioSocketImpl.write(NioSocketImpl.java:440) ~[?:?]
	at sun.nio.ch.NioSocketImpl$2.write(NioSocketImpl.java:819) ~[?:?]
	at java.net.Socket$SocketOutputStream.write(Socket.java:1195) ~[?:?]
	at io.helidon.common.buffers.FixedBufferData.writeTo(FixedBufferData.java:71) ~[helidon-common-buffers-4.0.0-RC1.jar:4.0.0-RC1]
	... 16 more

Steps to reproduce

I haven't found out a way how to produce the connection reset error in Helidon in a standalone fashion reliably. Having a websocket client and terminating the process or the operating system does not reliably produce the connection reset error.

How I came across the issue was when using a websocket written in rust which was attached to a modified videogame client (the client). When closing the game with a still open connection a connection reset error always is thrown.

Java code:

@ServerEndpoint(value = "/websocket/{id}")
public class WebsocketEndpoint {

    protected Map<String, List<String>> messageMap = new HashMap<>();

    @OnOpen
    public void onOpen(@PathParam("id") String id, Session session) {
        messageMap.put(id, new ArrayList<>());
    }

    @OnClose
    public void onclose(@PathParam("id") String id) {
        messageMap.remove(id);
    }
}
@m0mus m0mus added websocket WebSocket in Helidon bug Something isn't working P2 labels Oct 5, 2023
@m0mus m0mus added the 4.x Version 4.x label Oct 5, 2023
@spericas
Copy link
Member

@Neoministein Based on you detailed description, seems like something that should be fixed in Tyrus. If the exception is not caught while attempting to send that closing frame, there's not much Helidon can do.

Do you agree with this analysis @jansupol?

@Neoministein
Copy link
Author

Neoministein commented Oct 13, 2023

@Neoministein Based on your detailed description, seems like something that should be fixed in Tyrus. If the exception is not caught while attempting to send that closing frame, there's not much Helidon can do.

Makes sense. I am not entirely familiar with Helidon's integration with Tyrus that's why I thought it might be possible to fix it on Helidon's side. I'll open an issue on the Tyrus GitHub.

When fixing it in Tyrus. Would the change need to be back merged to the Tyrus branch 2.0.X or is there a short-term plan already to update Helidon to the Jakarta WebSocket API to version 2.1?

@spericas
Copy link
Member

spericas commented Oct 13, 2023

Helidon 2.x is javax based and Helidon 3.x is jakarta based. It depends on how it is fixed in Tyrus. @jansupol is the Tyrus lead and he'll take a look at this shortly.

@jansupol
Copy link
Contributor

jansupol commented Oct 17, 2023

So basically the issue is that @OnClose is not invoked. I'll provide a fix to Tyrus. To all Tyrus branches 1.x, 2.0, 2.1.

@jansupol
Copy link
Contributor

eclipse-ee4j/tyrus#847

@spericas
Copy link
Member

Will close this issue and create a new one to integrate the latest Tyrus when available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4.x Version 4.x bug Something isn't working P2 websocket WebSocket in Helidon
Projects
Archived in project
Development

No branches or pull requests

4 participants