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

喜欢而已 提交于 2019-12-04 03:05:09

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) {
   ...
}

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.

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));
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!