Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java WebSockets: The remote endpoint was in state [TEXT_FULL_WRITING]

I am trying to implement some application based on websockets which will communicate with JS clients quite intensively.

The code to send the message is quite primitive:

synchronized (session) {
    if (session.isOpen()) {
        session.getBasicRemote().sendText(message);
    }
}

For rare sending it works just fine, but when few threads are trying to send some messages by the same session (socket), next exception is thrown (please note that it is not multithreading issue because code block is synchronized by session):

java.lang.IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.checkState(WsRemoteEndpointImplBase.java:1015)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.textStart(WsRemoteEndpointImplBase.java:978)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendString(WsRemoteEndpointImplBase.java:161)
at org.apache.tomcat.websocket.WsRemoteEndpointBasic.sendText(WsRemoteEndpointBasic.java:37)

Google is not rich of such type of exceptions at the moment and after beating few hours on this issue, still no solution.

Java 7.0.21, tested on Tomcat 7.0.52 and Tomcat 8.0.3.

Any answer is highly appreciated! Thanks in advance.

UPDATE 3/11/2014: I tested my application with Jetty 9.1 and this exception didn't occur. I assume that this is Tomcat implementation bug.

like image 801
walv Avatar asked Mar 07 '14 17:03

walv


3 Answers

I just ran into this issue today, the accepted answer wasn't the resolution for me. I tried synchronizing every call to the remote endpoint that was in my code, which was only 4 instances. That did not fix it either. I also tried updating to the latest tomcat version which at this time was 9.0.24 which did not fix it.

It seams the source of my issue is that in a single websocket message request that came in, I happen to send two DIFFERENT messages (on purpose) during the request. I verified both sendText calls were properly synchronized, they were getting called about 0.001 milliseconds a part or less in different blocks.

My solution that I worked up real quick was to use the Async version of the remote endpoint, and just make sure the last msg's future was done by the time the next msg is requested to be sent. I wasn't thrilled about this but it did fix the issue... here is the class I wrote and I just now reference this object anytime I want to send something over the websocket without requiring the code in a sync block, since the send* methods on this class are already synchronized. Hope this helps someone down the line.

NOTE: I did not synchronize anything other than the send* so not sure if Ping/Pong will have the same issue or not, I've never used those.

public class WebSocketEndpointAsync implements RemoteEndpoint.Async {
    private final Session _session;
    private final Async _ep;
    private Future<Void> _lastFuture = null;

    public WebSocketEndpointAsync(Session session, Async ep)
    {
        _session = session;
        _ep = ep;
    }

    @Override public long getSendTimeout() { return _ep.getSendTimeout(); }
    @Override public void setSendTimeout(long timeout) { _ep.setSendTimeout(timeout); }
    @Override public void setBatchingAllowed(boolean allowed) throws IOException { _ep.setBatchingAllowed(allowed); }
    @Override public boolean getBatchingAllowed() { return _ep.getBatchingAllowed(); }
    @Override public void flushBatch() throws IOException { _ep.flushBatch(); }
    @Override public void sendPing(ByteBuffer byteBuffer) throws IOException, IllegalArgumentException { _ep.sendPing(byteBuffer); }
    @Override public void sendPong(ByteBuffer byteBuffer) throws IOException, IllegalArgumentException { _ep.sendPong(byteBuffer); }

    @Override public void sendText(String s, SendHandler sendHandler) { throw new UnsupportedOperationException(); }
    @Override public void sendBinary(ByteBuffer byteBuffer, SendHandler sendHandler) { throw new UnsupportedOperationException(); }
    @Override public void sendObject(Object o, SendHandler sendHandler) { throw new UnsupportedOperationException(); }

    protected synchronized void checkLastSendComplete() {
        if (_lastFuture != null) {
            try {
                if (!_lastFuture.isDone()) {
                    // Only one write to the websocket can happen at a time, so we need to make sure the last one completed
                    // else we get ...
                    // java.lang.IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method
                    do { Thread.sleep(1); }
                    while (!_lastFuture.isDone());
                }
                // Get the result to ensure
                var ignore = _lastFuture.get();
            }
            catch (InterruptedException ie) { }
            catch (ExecutionException ee) { }
        }
    }
    @Override
    public synchronized Future<Void> sendText(String text) {
        checkLastSendComplete();
        return (_lastFuture = _ep.sendText(text));
    }

    @Override
    public synchronized Future<Void> sendBinary(ByteBuffer byteBuffer) {
        checkLastSendComplete();
        return (_lastFuture = _ep.sendBinary(byteBuffer));
    }

    @Override
    public synchronized Future<Void> sendObject(Object obj) {
        checkLastSendComplete();
        return (_lastFuture = _ep.sendObject(obj));
    }
}
like image 92
xer21 Avatar answered Oct 29 '22 17:10

xer21


I found this: https://bz.apache.org/bugzilla/show_bug.cgi?id=56026

seems tomcat has done something unexpected, as a workaround you must synchronize all session.sendxxx calls no matter whether it's async.

like image 43
bulletProofCat Avatar answered Oct 29 '22 16:10

bulletProofCat


OK, this is not a Tomcat issue but my fault.

My onMessage function returned a string, what means that I echoed the message back. As result, those part of code was not synced.

Bad:

@OnMessage
public String onMessage(String message, Session session) {
   ...
   return message;
}

Good:

@OnMessage
public void onMessage(String message, Session session) {
   ...
}
like image 44
walv Avatar answered Oct 29 '22 18:10

walv